From f1eacf0e07759b57d100dbf5d83c70e4028bcb54 Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Tue, 11 Sep 2012 19:53:38 +0200 Subject: 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. --- Source/CMakeLists.txt | 6 + Source/cmGeneratorExpression.cxx | 240 ++------- Source/cmGeneratorExpression.h | 21 +- Source/cmGeneratorExpressionEvaluator.cxx | 568 +++++++++++++++++++++ Source/cmGeneratorExpressionEvaluator.h | 118 +++++ Source/cmGeneratorExpressionLexer.cxx | 85 +++ Source/cmGeneratorExpressionLexer.h | 58 +++ Source/cmGeneratorExpressionParser.cxx | 235 +++++++++ Source/cmGeneratorExpressionParser.h | 45 ++ .../RunCMake/GeneratorExpression/BadAND-stderr.txt | 31 +- Tests/RunCMake/GeneratorExpression/BadAND.cmake | 3 + .../GeneratorExpression/BadCONFIG-stderr.txt | 36 ++ Tests/RunCMake/GeneratorExpression/BadCONFIG.cmake | 4 + .../RunCMake/GeneratorExpression/BadNOT-stderr.txt | 32 +- Tests/RunCMake/GeneratorExpression/BadNOT.cmake | 3 + .../RunCMake/GeneratorExpression/BadOR-stderr.txt | 31 +- Tests/RunCMake/GeneratorExpression/BadOR.cmake | 3 + bootstrap | 3 + 18 files changed, 1321 insertions(+), 201 deletions(-) create mode 100644 Source/cmGeneratorExpressionEvaluator.cxx create mode 100644 Source/cmGeneratorExpressionEvaluator.h create mode 100644 Source/cmGeneratorExpressionLexer.cxx create mode 100644 Source/cmGeneratorExpressionLexer.h create mode 100644 Source/cmGeneratorExpressionParser.cxx create mode 100644 Source/cmGeneratorExpressionParser.h diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index e79689b..ba71c8a 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 92bbf1d..198b43a 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; -} -//---------------------------------------------------------------------------- -static bool cmGeneratorExpressionBool(const char* c, std::string& result, - const char* name, - const char* a, const char* b) -{ - result = a; - while((c[0] == '0' || c[0] == '1') && (c[1] == ',' || c[1] == '>')) - { - if(c[0] == b[0]) { result = b; } - c += 2; - } - if(c[0]) - { - result = name; - result += " requires one or more comma-separated '0' or '1' values."; - return false; - } - return true; + cmGeneratorExpressionParser p(tokens); + p.Parse(this->Evaluators); } //---------------------------------------------------------------------------- -bool cmGeneratorExpression::Evaluate(const char* expr, std::string& result) +const char *cmGeneratorExpression::Evaluate( + cmMakefile* mf, const char* config, bool quiet) { - if(this->TargetInfo.find(expr)) + if (!this->NeedsParsing) { - if(!this->EvaluateTargetInfo(result)) - { - return false; - } + return this->Input; } - 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, "$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) { - const char* c = expr+6; - if((c[0] != '0' && c[0] != '1') || c[1] != '>' || c[2]) + this->Output += (*it)->Evaluate(&context); + if (context.HadError) { - result = "NOT requires exactly one '0' or '1' value."; - return false; + this->Output = ""; + break; } - result = c[0] == '1'? "0" : "1"; - } - 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; + + this->Targets = context.Targets; + // TODO: Return a std::string from here instead? + return this->Output.c_str(); } //---------------------------------------------------------------------------- -bool cmGeneratorExpression::EvaluateTargetInfo(std::string& result) +cmGeneratorExpression::~cmGeneratorExpression() { - // 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); + std::vector::const_iterator it + = this->Evaluators.begin(); + const std::vector::const_iterator end + = this->Evaluators.end(); - // 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") + for ( ; it != end; ++it) { - result = cmSystemTools::GetFilenamePath(result); + delete *it; } - return true; } diff --git a/Source/cmGeneratorExpression.h b/Source/cmGeneratorExpression.h index a023eb0..00e6907 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 0000000..acc844a --- /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 0000000..5163ca0 --- /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 0000000..cd71ec0 --- /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 0000000..5f16712 --- /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 0000000..2a5cc7a --- /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 0000000..28f1441 --- /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 ced21d8..36302db 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 7926540..265e414 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 7c86b25..1cfbf40 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 0c13f89..c27ea5f 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 5721f5f..32169c5 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 452293b..c2dada3 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 72ef2dd..d4ccab7 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 f16f56a..0813400 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 ec5745d..23134d0 100755 --- a/bootstrap +++ b/bootstrap @@ -202,6 +202,9 @@ CMAKE_CXX_SOURCES="\ cmInstallDirectoryGenerator \ cmGeneratedFileStream \ cmGeneratorTarget \ + cmGeneratorExpressionEvaluator \ + cmGeneratorExpressionLexer \ + cmGeneratorExpressionParser \ cmGeneratorExpression \ cmGlobalGenerator \ cmLocalGenerator \ -- cgit v0.12 From 91011bd217726f73e362b10d77a6638977d6a781 Mon Sep 17 00:00:00 2001 From: Stephen Kelly Date: Wed, 12 Sep 2012 15:11:25 +0200 Subject: cmGeneratorExpression: Port users to two-stage processing Removing the Process() API and removing the parameters from the constructor will allow cmGeneratorExpressions to be cached and evaluated with multiple configs for example, such as when evaluating target properties. This requires the creation of a new compiled representation of cmGeneratorExpression. The cmListFileBacktrace remains in the constructor so that we can record where a particular generator expression appeared in the CMakeLists file. --- Source/cmCustomCommandGenerator.cxx | 7 ++-- Source/cmGeneratorExpression.cxx | 65 ++++++++++++++++++++++--------------- Source/cmGeneratorExpression.h | 54 +++++++++++++++++++----------- Source/cmTarget.cxx | 17 +++++++--- Source/cmTestGenerator.cxx | 8 ++--- 5 files changed, 95 insertions(+), 56 deletions(-) diff --git a/Source/cmCustomCommandGenerator.cxx b/Source/cmCustomCommandGenerator.cxx index a650129..07df7d5 100644 --- a/Source/cmCustomCommandGenerator.cxx +++ b/Source/cmCustomCommandGenerator.cxx @@ -21,7 +21,7 @@ cmCustomCommandGenerator::cmCustomCommandGenerator( cmCustomCommand const& cc, const char* config, cmMakefile* mf): CC(cc), Config(config), Makefile(mf), LG(mf->GetLocalGenerator()), OldStyle(cc.GetEscapeOldStyle()), MakeVars(cc.GetEscapeAllowMakeVars()), - GE(new cmGeneratorExpression(mf, config, cc.GetBacktrace())) + GE(new cmGeneratorExpression(cc.GetBacktrace())) { } @@ -47,7 +47,7 @@ std::string cmCustomCommandGenerator::GetCommand(unsigned int c) const { return target->GetLocation(this->Config); } - return this->GE->Process(argv0); + return this->GE->Parse(argv0).Evaluate(this->Makefile, this->Config); } //---------------------------------------------------------------------------- @@ -58,7 +58,8 @@ cmCustomCommandGenerator cmCustomCommandLine const& commandLine = this->CC.GetCommandLines()[c]; for(unsigned int j=1;j < commandLine.size(); ++j) { - std::string arg = this->GE->Process(commandLine[j]); + std::string arg = this->GE->Parse(commandLine[j]).Evaluate(this->Makefile, + this->Config); cmd += " "; if(this->OldStyle) { diff --git a/Source/cmGeneratorExpression.cxx b/Source/cmGeneratorExpression.cxx index 198b43a..0885616 100644 --- a/Source/cmGeneratorExpression.cxx +++ b/Source/cmGeneratorExpression.cxx @@ -22,48 +22,50 @@ //---------------------------------------------------------------------------- cmGeneratorExpression::cmGeneratorExpression( - cmMakefile* mf, const char* config, - cmListFileBacktrace const& backtrace, bool quiet): - Makefile(mf), Config(config), Backtrace(backtrace), Quiet(quiet), - NeedsParsing(true) + cmListFileBacktrace const& backtrace): + Backtrace(backtrace), CompiledExpression(0) { } //---------------------------------------------------------------------------- -const char* cmGeneratorExpression::Process(std::string const& input) +const cmCompiledGeneratorExpression & +cmGeneratorExpression::Parse(std::string const& input) { - return this->Process(input.c_str()); + return this->Parse(input.c_str()); } //---------------------------------------------------------------------------- -const char* cmGeneratorExpression::Process(const char* input) +const cmCompiledGeneratorExpression & +cmGeneratorExpression::Parse(const char* input) { - this->Parse(input); - return this->Evaluate(this->Makefile, this->Config, this->Quiet); -} - -//---------------------------------------------------------------------------- -void cmGeneratorExpression::Parse(const char* input) -{ - this->Evaluators.clear(); - - this->Input = input; cmGeneratorExpressionLexer l; - std::vector tokens = l.Tokenize(this->Input); - this->NeedsParsing = l.GetSawGeneratorExpression(); + std::vector tokens = l.Tokenize(input); + bool needsParsing = l.GetSawGeneratorExpression(); + std::vector evaluators; - if (!this->NeedsParsing) + if (needsParsing) { - return; + cmGeneratorExpressionParser p(tokens); + p.Parse(evaluators); } - cmGeneratorExpressionParser p(tokens); - p.Parse(this->Evaluators); + delete this->CompiledExpression; + this->CompiledExpression = new cmCompiledGeneratorExpression( + this->Backtrace, + evaluators, + input, + needsParsing); + return *this->CompiledExpression; +} + +cmGeneratorExpression::~cmGeneratorExpression() +{ + delete this->CompiledExpression; } //---------------------------------------------------------------------------- -const char *cmGeneratorExpression::Evaluate( - cmMakefile* mf, const char* config, bool quiet) +const char *cmCompiledGeneratorExpression::Evaluate( + cmMakefile* mf, const char* config, bool quiet) const { if (!this->NeedsParsing) { @@ -99,8 +101,19 @@ const char *cmGeneratorExpression::Evaluate( return this->Output.c_str(); } +cmCompiledGeneratorExpression::cmCompiledGeneratorExpression( + cmListFileBacktrace const& backtrace, + const std::vector &evaluators, + const char *input, bool needsParsing) + : Backtrace(backtrace), Evaluators(evaluators), Input(input), + NeedsParsing(needsParsing) +{ + +} + + //---------------------------------------------------------------------------- -cmGeneratorExpression::~cmGeneratorExpression() +cmCompiledGeneratorExpression::~cmCompiledGeneratorExpression() { std::vector::const_iterator it = this->Evaluators.begin(); diff --git a/Source/cmGeneratorExpression.h b/Source/cmGeneratorExpression.h index 00e6907..b8467c2 100644 --- a/Source/cmGeneratorExpression.h +++ b/Source/cmGeneratorExpression.h @@ -21,6 +21,8 @@ class cmListFileBacktrace; struct cmGeneratorExpressionEvaluator; +class cmCompiledGeneratorExpression; + /** \class cmGeneratorExpression * \brief Evaluate generate-time query expression syntax. * @@ -33,34 +35,48 @@ struct cmGeneratorExpressionEvaluator; class cmGeneratorExpression { public: - /** Construct with an evaluation context and configuration. */ - cmGeneratorExpression(cmMakefile* mf, const char* config, - cmListFileBacktrace const& backtrace, - bool quiet = false); - + /** Construct. */ + cmGeneratorExpression(cmListFileBacktrace const& backtrace); ~cmGeneratorExpression(); - /** Evaluate generator expressions in a string. */ - const char* Process(std::string const& input); - const char* Process(const char* input); + const cmCompiledGeneratorExpression& Parse(std::string const& input); + const cmCompiledGeneratorExpression& Parse(const char* input); + +private: + cmGeneratorExpression(const cmGeneratorExpression &); + void operator=(const cmGeneratorExpression &); + + cmListFileBacktrace const& Backtrace; + cmCompiledGeneratorExpression *CompiledExpression; +}; - void Parse(const char* input); +class cmCompiledGeneratorExpression +{ +public: const char* Evaluate(cmMakefile* mf, const char* config, - bool quiet = false); + bool quiet = false) const; /** Get set of targets found during evaluations. */ std::set const& GetTargets() const { return this->Targets; } + + ~cmCompiledGeneratorExpression(); + private: - std::vector Evaluators; - cmMakefile* Makefile; - const char* Config; - cmListFileBacktrace const& Backtrace; - bool Quiet; + cmCompiledGeneratorExpression(cmListFileBacktrace const& backtrace, + const std::vector &evaluators, + const char *input, bool needsParsing); - std::set Targets; - const char* Input; - bool NeedsParsing; + friend class cmGeneratorExpression; + + cmCompiledGeneratorExpression(const cmCompiledGeneratorExpression &); + void operator=(const cmCompiledGeneratorExpression &); + + cmListFileBacktrace const& Backtrace; + const std::vector Evaluators; + const char* const Input; + const bool NeedsParsing; - std::string Output; + mutable std::set Targets; + mutable std::string Output; }; diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index 789713f..9a3812c 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -1623,7 +1623,11 @@ cmTargetTraceDependencies { // Transform command names that reference targets built in this // project to corresponding target-level dependencies. - cmGeneratorExpression ge(this->Makefile, 0, cc.GetBacktrace(), true); + cmGeneratorExpression ge(cc.GetBacktrace()); + + // Add target-level dependencies referenced by generator expressions. + std::set targets; + for(cmCustomCommandLines::const_iterator cit = cc.GetCommandLines().begin(); cit != cc.GetCommandLines().end(); ++cit) { @@ -1645,12 +1649,17 @@ cmTargetTraceDependencies for(cmCustomCommandLine::const_iterator cli = cit->begin(); cli != cit->end(); ++cli) { - ge.Process(*cli); + const cmCompiledGeneratorExpression &cge = ge.Parse(*cli); + cge.Evaluate(this->Makefile, 0, true); + std::set geTargets = cge.GetTargets(); + for(std::set::const_iterator it = geTargets.begin(); + it != geTargets.end(); ++it) + { + targets.insert(*it); + } } } - // Add target-level dependencies referenced by generator expressions. - std::set targets = ge.GetTargets(); for(std::set::iterator ti = targets.begin(); ti != targets.end(); ++ti) { diff --git a/Source/cmTestGenerator.cxx b/Source/cmTestGenerator.cxx index e0892b2..2f650e7 100644 --- a/Source/cmTestGenerator.cxx +++ b/Source/cmTestGenerator.cxx @@ -91,8 +91,7 @@ void cmTestGenerator::GenerateScriptForConfig(std::ostream& os, this->TestGenerated = true; // Set up generator expression evaluation context. - cmMakefile* mf = this->Test->GetMakefile(); - cmGeneratorExpression ge(mf, config, this->Test->GetBacktrace()); + cmGeneratorExpression ge(this->Test->GetBacktrace()); // Start the test command. os << indent << "ADD_TEST(" << this->Test->GetName() << " "; @@ -103,6 +102,7 @@ void cmTestGenerator::GenerateScriptForConfig(std::ostream& os, // Check whether the command executable is a target whose name is to // be translated. std::string exe = command[0]; + cmMakefile* mf = this->Test->GetMakefile(); cmTarget* target = mf->FindTargetToUse(exe.c_str()); if(target && target->GetType() == cmTarget::EXECUTABLE) { @@ -112,7 +112,7 @@ void cmTestGenerator::GenerateScriptForConfig(std::ostream& os, else { // Use the command name given. - exe = ge.Process(exe.c_str()); + exe = ge.Parse(exe.c_str()).Evaluate(mf, config); cmSystemTools::ConvertToUnixSlashes(exe); } @@ -122,7 +122,7 @@ void cmTestGenerator::GenerateScriptForConfig(std::ostream& os, for(std::vector::const_iterator ci = command.begin()+1; ci != command.end(); ++ci) { - os << " " << lg->EscapeForCMake(ge.Process(*ci)); + os << " " << lg->EscapeForCMake(ge.Parse(*ci).Evaluate(mf, config)); } // Finish the test command. -- cgit v0.12