diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index e79689b08..ba71c8aeb 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -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 diff --git a/Source/cmGeneratorExpression.cxx b/Source/cmGeneratorExpression.cxx index 92bbf1d82..198b43a1f 100644 --- a/Source/cmGeneratorExpression.cxx +++ b/Source/cmGeneratorExpression.cxx @@ -16,18 +16,17 @@ #include +#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("^\\$$"); - this->TestConfig.compile("^\\$$"); } //---------------------------------------------------------------------------- @@ -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 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::const_iterator it + = this->Evaluators.begin(); + const std::vector::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::const_iterator it + = this->Evaluators.begin(); + const std::vector::const_iterator end + = this->Evaluators.end(); + + for ( ; it != end; ++it) { - if(!this->EvaluateTargetInfo(result)) - { - return false; - } + delete *it; } - else if(strcmp(expr, "$") == 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, "$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; } diff --git a/Source/cmGeneratorExpression.h b/Source/cmGeneratorExpression.h index a023eb0c9..00e6907ef 100644 --- a/Source/cmGeneratorExpression.h +++ b/Source/cmGeneratorExpression.h @@ -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 const& GetTargets() const { return this->Targets; } private: + std::vector Evaluators; cmMakefile* Makefile; const char* Config; cmListFileBacktrace const& Backtrace; bool Quiet; - std::vector Data; - std::stack Barriers; - cmsys::RegularExpression TargetInfo; - cmsys::RegularExpression TestConfig; + std::set Targets; - bool Evaluate(); - bool Evaluate(const char* expr, std::string& result); - bool EvaluateTargetInfo(std::string& result); + const char* Input; + bool NeedsParsing; + + std::string Output; }; diff --git a/Source/cmGeneratorExpressionEvaluator.cxx b/Source/cmGeneratorExpressionEvaluator.cxx new file mode 100644 index 000000000..acc844a39 --- /dev/null +++ b/Source/cmGeneratorExpressionEvaluator.cxx @@ -0,0 +1,568 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2012 Stephen Kelly + + 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 ¶meters, + 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 &, + 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 &, + 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 ¶meters, \ + cmGeneratorExpressionContext *context, \ + const GeneratorExpressionContent *content) const \ + { \ + std::vector::const_iterator it = parameters.begin(); \ + const std::vector::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 ¶meters, + cmGeneratorExpressionContext *context, + const GeneratorExpressionContent *content) const + { + if (*parameters.begin() != "0" && *parameters.begin() != "1") + { + reportError(context, content->GetOriginalExpression(), + "$ 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 &, + 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 ¶meters, + 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 +struct TargetFilesystemArtifactResultCreator +{ + static std::string Create(cmTarget* target, + cmGeneratorExpressionContext *context, + const GeneratorExpressionContent *content); +}; + +//---------------------------------------------------------------------------- +template<> +struct TargetFilesystemArtifactResultCreator +{ + 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 +{ + 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 +{ + static std::string Create(cmTarget* target, + cmGeneratorExpressionContext *context, + const GeneratorExpressionContent *) + { + return target->GetFullPath(context->Config, false, true); + } +}; + + +//---------------------------------------------------------------------------- +template +struct TargetFilesystemArtifactResultGetter +{ + static std::string Get(const std::string &result); +}; + +//---------------------------------------------------------------------------- +template<> +struct TargetFilesystemArtifactResultGetter +{ + static std::string Get(const std::string &result) + { return cmSystemTools::GetFilenameName(result); } +}; + +//---------------------------------------------------------------------------- +template<> +struct TargetFilesystemArtifactResultGetter +{ + static std::string Get(const std::string &result) + { return cmSystemTools::GetFilenamePath(result); } +}; + +//---------------------------------------------------------------------------- +template<> +struct TargetFilesystemArtifactResultGetter +{ + static std::string Get(const std::string &result) + { return result; } +}; + +//---------------------------------------------------------------------------- +template +struct TargetFilesystemArtifact : public cmGeneratorExpressionNode +{ + TargetFilesystemArtifact() {} + + virtual int NumExpectedParameters() const { return 1; } + + std::string Evaluate(const std::vector ¶meters, + 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::Create( + target, + context, + content); + if (context->HadError) + { + return std::string(); + } + return + TargetFilesystemArtifactResultGetter::Get(result); + } +}; + +//---------------------------------------------------------------------------- +static const +TargetFilesystemArtifact targetFileNode; +static const +TargetFilesystemArtifact targetLinkerFileNode; +static const +TargetFilesystemArtifact targetSoNameFileNode; +static const +TargetFilesystemArtifact targetFileNameNode; +static const +TargetFilesystemArtifact targetLinkerFileNameNode; +static const +TargetFilesystemArtifact targetSoNameFileNameNode; +static const +TargetFilesystemArtifact targetFileDirNode; +static const +TargetFilesystemArtifact targetLinkerFileDirNode; +static const +TargetFilesystemArtifact 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 ¬Node; + 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::const_iterator it + = this->IdentifierChildren.begin(); + const std::vector::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 >::const_iterator + pit = this->ParamChildren.begin(); + const + std::vector >::const_iterator + pend = this->ParamChildren.end(); + for ( ; pit != pend; ++pit) + { + if (!result.empty()) + { + result += ","; + } + + std::vector::const_iterator it + = pit->begin(); + const std::vector::const_iterator end + = pit->end(); + for ( ; it != end; ++it) + { + result += (*it)->Evaluate(context); + if (context->HadError) + { + return std::string(); + } + } + } + return result; + } + + std::vector parameters; + { + std::vector >::const_iterator + pit = this->ParamChildren.begin(); + const + std::vector >::const_iterator + pend = this->ParamChildren.end(); + for ( ; pit != pend; ++pit) + { + std::string parameter; + std::vector::const_iterator it = + pit->begin(); + const std::vector::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 &c) +{ + std::vector::const_iterator it + = c.begin(); + const std::vector::const_iterator end + = c.end(); + for ( ; it != end; ++it) + { + delete *it; + } +} + +//---------------------------------------------------------------------------- +GeneratorExpressionContent::~GeneratorExpressionContent() +{ + deleteAll(this->IdentifierChildren); + + typedef std::vector EvaluatorVector; + typedef std::vector TokenVector; + std::vector::const_iterator pit = + this->ParamChildren.begin(); + const std::vector::const_iterator pend = + this->ParamChildren.end(); + for ( ; pit != pend; ++pit) + { + deleteAll(*pit); + } +} diff --git a/Source/cmGeneratorExpressionEvaluator.h b/Source/cmGeneratorExpressionEvaluator.h new file mode 100644 index 000000000..5163ca002 --- /dev/null +++ b/Source/cmGeneratorExpressionEvaluator.h @@ -0,0 +1,118 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2012 Stephen Kelly + + 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 +#include + +//---------------------------------------------------------------------------- +struct cmGeneratorExpressionContext +{ + cmListFileBacktrace Backtrace; + std::set 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 identifier) + { + this->IdentifierChildren = identifier; + } + + void SetParameters( + std::vector > parameters) + { + this->ParamChildren = parameters; + } + + Type GetType() const + { + return cmGeneratorExpressionEvaluator::Generator; + } + + std::string Evaluate(cmGeneratorExpressionContext *context) const; + + std::string GetOriginalExpression() const; + + ~GeneratorExpressionContent(); + +private: + std::vector IdentifierChildren; + std::vector > ParamChildren; + const char *StartContent; + unsigned int ContentLength; +}; + +#endif diff --git a/Source/cmGeneratorExpressionLexer.cxx b/Source/cmGeneratorExpressionLexer.cxx new file mode 100644 index 000000000..cd71ec033 --- /dev/null +++ b/Source/cmGeneratorExpressionLexer.cxx @@ -0,0 +1,85 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2012 Stephen Kelly + + 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 &result) +{ + if (upto != c) + { + result.push_back(cmGeneratorExpressionToken( + cmGeneratorExpressionToken::Text, upto, c - upto)); + } +} + +//---------------------------------------------------------------------------- +std::vector +cmGeneratorExpressionLexer::Tokenize(const char *input) +{ + std::vector 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; +} diff --git a/Source/cmGeneratorExpressionLexer.h b/Source/cmGeneratorExpressionLexer.h new file mode 100644 index 000000000..5f16712fb --- /dev/null +++ b/Source/cmGeneratorExpressionLexer.h @@ -0,0 +1,58 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2012 Stephen Kelly + + 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 + +//---------------------------------------------------------------------------- +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 Tokenize(const char *input); + + bool GetSawGeneratorExpression() const + { + return this->SawGeneratorExpression; + } + +private: + bool SawBeginExpression; + bool SawGeneratorExpression; +}; + +#endif diff --git a/Source/cmGeneratorExpressionParser.cxx b/Source/cmGeneratorExpressionParser.cxx new file mode 100644 index 000000000..2a5cc7aae --- /dev/null +++ b/Source/cmGeneratorExpressionParser.cxx @@ -0,0 +1,235 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2012 Stephen Kelly + + 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 &tokens) + : Tokens(tokens), NestingLevel(0) +{ +} + +//---------------------------------------------------------------------------- +void cmGeneratorExpressionParser::Parse( + std::vector &result) +{ + it = this->Tokens.begin(); + + while (this->it != this->Tokens.end()) + { + this->ParseContent(result); + } +} + +//---------------------------------------------------------------------------- +static void extendText(std::vector &result, + std::vector::const_iterator it) +{ + if (result.size() > 0 + && (*(result.end() - 1))->GetType() + == cmGeneratorExpressionEvaluator::Text) + { + TextContent *textContent = static_cast(*(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 &result, + const std::vector &contents) +{ + if (result.size() > 0 + && (*(result.end() - 1))->GetType() + == cmGeneratorExpressionEvaluator::Text + && (*contents.begin())->GetType() + == cmGeneratorExpressionEvaluator::Text) + { + TextContent *textContent = static_cast(*(result.end() - 1)); + textContent->Extend( + static_cast(*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 &result) +{ + unsigned int nestedLevel = this->NestingLevel; + ++this->NestingLevel; + + std::vector::const_iterator startToken + = this->it - 1; + + std::vector 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 > parameters; + std::vector::const_iterator> + commaTokens; + std::vector::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 EvaluatorVector; + typedef std::vector TokenVector; + std::vector::const_iterator pit = parameters.begin(); + const std::vector::const_iterator pend = + parameters.end(); + std::vector::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 &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(*(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? +} diff --git a/Source/cmGeneratorExpressionParser.h b/Source/cmGeneratorExpressionParser.h new file mode 100644 index 000000000..28f14410f --- /dev/null +++ b/Source/cmGeneratorExpressionParser.h @@ -0,0 +1,45 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2012 Stephen Kelly + + 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 +#include + +#include "cmListFileCache.h" + +class cmMakefile; +class cmTarget; +struct cmGeneratorExpressionEvaluator; + +//---------------------------------------------------------------------------- +struct cmGeneratorExpressionParser +{ + cmGeneratorExpressionParser( + const std::vector &tokens); + + void Parse(std::vector &result); + +private: + void ParseContent(std::vector &); + void ParseGeneratorExpression( + std::vector &); + +private: + std::vector::const_iterator it; + const std::vector Tokens; + unsigned int NestingLevel; +}; + +#endif diff --git a/Tests/RunCMake/GeneratorExpression/BadAND-stderr.txt b/Tests/RunCMake/GeneratorExpression/BadAND-stderr.txt index ced21d806..36302db9b 100644 --- a/Tests/RunCMake/GeneratorExpression/BadAND-stderr.txt +++ b/Tests/RunCMake/GeneratorExpression/BadAND-stderr.txt @@ -1,9 +1,18 @@ +CMake Error at BadAND.cmake:1 \(add_custom_target\): + Error evaluating generator expression: + + \$ + + \$ 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 requires one or more comma-separated '0' or '1' values. + Parameters to \$ 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 requires one or more comma-separated '0' or '1' values. + Parameters to \$ 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: + + \$ + + Parameters to \$ 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: + + \$ + + Parameters to \$ must resolve to either '0' or '1'. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\)$ diff --git a/Tests/RunCMake/GeneratorExpression/BadAND.cmake b/Tests/RunCMake/GeneratorExpression/BadAND.cmake index 792654046..265e4149b 100644 --- a/Tests/RunCMake/GeneratorExpression/BadAND.cmake +++ b/Tests/RunCMake/GeneratorExpression/BadAND.cmake @@ -1,4 +1,7 @@ add_custom_target(check ALL COMMAND check + $ $ $ + $ + $ VERBATIM) diff --git a/Tests/RunCMake/GeneratorExpression/BadCONFIG-stderr.txt b/Tests/RunCMake/GeneratorExpression/BadCONFIG-stderr.txt index 7c86b250f..1cfbf404e 100644 --- a/Tests/RunCMake/GeneratorExpression/BadCONFIG-stderr.txt +++ b/Tests/RunCMake/GeneratorExpression/BadCONFIG-stderr.txt @@ -1,8 +1,44 @@ +CMake Error at BadCONFIG.cmake:1 \(add_custom_target\): + Error evaluating generator expression: + + \$ + + \$ 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: \$ + 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: + + \$ + + \$ 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: + + \$ + + 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: + + \$ + Expression syntax not recognized. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\)$ diff --git a/Tests/RunCMake/GeneratorExpression/BadCONFIG.cmake b/Tests/RunCMake/GeneratorExpression/BadCONFIG.cmake index 0c13f8944..c27ea5f73 100644 --- a/Tests/RunCMake/GeneratorExpression/BadCONFIG.cmake +++ b/Tests/RunCMake/GeneratorExpression/BadCONFIG.cmake @@ -1,3 +1,7 @@ add_custom_target(check ALL COMMAND check + $ $ + $ + $ + $<$:foo> VERBATIM) diff --git a/Tests/RunCMake/GeneratorExpression/BadNOT-stderr.txt b/Tests/RunCMake/GeneratorExpression/BadNOT-stderr.txt index 5721f5fe1..32169c537 100644 --- a/Tests/RunCMake/GeneratorExpression/BadNOT-stderr.txt +++ b/Tests/RunCMake/GeneratorExpression/BadNOT-stderr.txt @@ -1,9 +1,17 @@ CMake Error at BadNOT.cmake:1 \(add_custom_target\): + Error evaluating generator expression: + + \$ + + \$ 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 requires exactly one '0' or '1' value. + \$ 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 requires exactly one '0' or '1' value. + \$ 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 requires exactly one '0' or '1' value. + \$ 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: + + \$ + + \$ 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: + + \$ + + \$ parameter must resolve to exactly one '0' or '1' value. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\)$ diff --git a/Tests/RunCMake/GeneratorExpression/BadNOT.cmake b/Tests/RunCMake/GeneratorExpression/BadNOT.cmake index 452293b8d..c2dada3c2 100644 --- a/Tests/RunCMake/GeneratorExpression/BadNOT.cmake +++ b/Tests/RunCMake/GeneratorExpression/BadNOT.cmake @@ -1,5 +1,8 @@ add_custom_target(check ALL COMMAND check + $ $ $ $ + $ + $ VERBATIM) diff --git a/Tests/RunCMake/GeneratorExpression/BadOR-stderr.txt b/Tests/RunCMake/GeneratorExpression/BadOR-stderr.txt index 72ef2dd3c..d4ccab74c 100644 --- a/Tests/RunCMake/GeneratorExpression/BadOR-stderr.txt +++ b/Tests/RunCMake/GeneratorExpression/BadOR-stderr.txt @@ -1,9 +1,18 @@ +CMake Error at BadOR.cmake:1 \(add_custom_target\): + Error evaluating generator expression: + + \$ + + \$ 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 requires one or more comma-separated '0' or '1' values. + Parameters to \$ 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 requires one or more comma-separated '0' or '1' values. + Parameters to \$ 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: + + \$ + + Parameters to \$ 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: + + \$ + + Parameters to \$ must resolve to either '0' or '1'. Call Stack \(most recent call first\): CMakeLists.txt:3 \(include\)$ diff --git a/Tests/RunCMake/GeneratorExpression/BadOR.cmake b/Tests/RunCMake/GeneratorExpression/BadOR.cmake index f16f56a3c..0813400d6 100644 --- a/Tests/RunCMake/GeneratorExpression/BadOR.cmake +++ b/Tests/RunCMake/GeneratorExpression/BadOR.cmake @@ -1,4 +1,7 @@ add_custom_target(check ALL COMMAND check + $ $ $ + $ + $ VERBATIM) diff --git a/bootstrap b/bootstrap index ec5745df3..23134d091 100755 --- a/bootstrap +++ b/bootstrap @@ -202,6 +202,9 @@ CMAKE_CXX_SOURCES="\ cmInstallDirectoryGenerator \ cmGeneratedFileStream \ cmGeneratorTarget \ + cmGeneratorExpressionEvaluator \ + cmGeneratorExpressionLexer \ + cmGeneratorExpressionParser \ cmGeneratorExpression \ cmGlobalGenerator \ cmLocalGenerator \