| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing for details. */ |
| #include "cmCommandArgumentParserHelper.h" |
| |
| #include <cstring> |
| #include <iostream> |
| #include <sstream> |
| #include <utility> |
| |
| #include <cm/memory> |
| #include <cm/optional> |
| #include <cmext/string_view> |
| |
| #include "cmCommandArgumentLexer.h" |
| #include "cmListFileCache.h" |
| #include "cmMakefile.h" |
| #include "cmState.h" |
| #include "cmStringAlgorithms.h" |
| #include "cmSystemTools.h" |
| #include "cmValue.h" |
| |
| int cmCommandArgument_yyparse(yyscan_t yyscanner); |
| // |
| cmCommandArgumentParserHelper::cmCommandArgumentParserHelper() |
| { |
| this->FileLine = -1; |
| this->FileName = nullptr; |
| this->RemoveEmpty = true; |
| |
| this->NoEscapeMode = false; |
| this->ReplaceAtSyntax = false; |
| } |
| |
| cmCommandArgumentParserHelper::~cmCommandArgumentParserHelper() |
| { |
| this->CleanupParser(); |
| } |
| |
| void cmCommandArgumentParserHelper::SetLineFile(long line, const char* file) |
| { |
| this->FileLine = line; |
| this->FileName = file; |
| } |
| |
| const char* cmCommandArgumentParserHelper::AddString(const std::string& str) |
| { |
| if (str.empty()) { |
| return ""; |
| } |
| auto stVal = cm::make_unique<char[]>(str.size() + 1); |
| strcpy(stVal.get(), str.c_str()); |
| this->Variables.push_back(std::move(stVal)); |
| return this->Variables.back().get(); |
| } |
| |
| const char* cmCommandArgumentParserHelper::ExpandSpecialVariable( |
| const char* key, const char* var) |
| { |
| if (!key) { |
| return this->ExpandVariable(var); |
| } |
| if (!var) { |
| return ""; |
| } |
| if (strcmp(key, "ENV") == 0) { |
| std::string str; |
| if (cmSystemTools::GetEnv(var, str)) { |
| if (this->EscapeQuotes) { |
| return this->AddString(cmEscapeQuotes(str)); |
| } |
| return this->AddString(str); |
| } |
| return ""; |
| } |
| if (strcmp(key, "CACHE") == 0) { |
| if (cmValue c = |
| this->Makefile->GetState()->GetInitializedCacheValue(var)) { |
| if (this->EscapeQuotes) { |
| return this->AddString(cmEscapeQuotes(*c)); |
| } |
| return this->AddString(*c); |
| } |
| return ""; |
| } |
| std::ostringstream e; |
| e << "Syntax $" << key << "{} is not supported. " |
| << "Only ${}, $ENV{}, and $CACHE{} are allowed."; |
| this->SetError(e.str()); |
| return nullptr; |
| } |
| |
| const char* cmCommandArgumentParserHelper::ExpandVariable(const char* var) |
| { |
| if (!var) { |
| return nullptr; |
| } |
| if (this->FileLine >= 0 && strcmp(var, "CMAKE_CURRENT_LIST_LINE") == 0) { |
| std::string line; |
| cmListFileBacktrace bt = this->Makefile->GetBacktrace(); |
| cmListFileContext const& top = bt.Top(); |
| if (top.DeferId) { |
| line = cmStrCat("DEFERRED:"_s, *top.DeferId); |
| } else { |
| line = std::to_string(this->FileLine); |
| } |
| return this->AddString(line); |
| } |
| cmValue value = this->Makefile->GetDefinition(var); |
| if (!value) { |
| this->Makefile->MaybeWarnUninitialized(var, this->FileName); |
| if (!this->RemoveEmpty) { |
| return nullptr; |
| } |
| } |
| if (this->EscapeQuotes && value) { |
| return this->AddString(cmEscapeQuotes(*value)); |
| } |
| return this->AddString(value); |
| } |
| |
| const char* cmCommandArgumentParserHelper::ExpandVariableForAt(const char* var) |
| { |
| if (this->ReplaceAtSyntax) { |
| // try to expand the variable |
| const char* ret = this->ExpandVariable(var); |
| // if the return was 0 and we want to replace empty strings |
| // then return an empty string |
| if (!ret && this->RemoveEmpty) { |
| return this->AddString(""); |
| } |
| // if the ret was not 0, then return it |
| if (ret) { |
| return ret; |
| } |
| } |
| // at this point we want to put it back because of one of these cases: |
| // - this->ReplaceAtSyntax is false |
| // - this->ReplaceAtSyntax is true, but this->RemoveEmpty is false, |
| // and the variable was not defined |
| std::string ref = cmStrCat('@', var, '@'); |
| return this->AddString(ref); |
| } |
| |
| const char* cmCommandArgumentParserHelper::CombineUnions(const char* in1, |
| const char* in2) |
| { |
| if (!in1) { |
| return in2; |
| } |
| if (!in2) { |
| return in1; |
| } |
| size_t len = strlen(in1) + strlen(in2) + 1; |
| auto out = cm::make_unique<char[]>(len); |
| strcpy(out.get(), in1); |
| strcat(out.get(), in2); |
| this->Variables.push_back(std::move(out)); |
| return this->Variables.back().get(); |
| } |
| |
| void cmCommandArgumentParserHelper::AllocateParserType( |
| cmCommandArgumentParserHelper::ParserType* pt, const char* str, int len) |
| { |
| pt->str = nullptr; |
| if (len == 0) { |
| len = static_cast<int>(strlen(str)); |
| } |
| if (len == 0) { |
| return; |
| } |
| auto out = cm::make_unique<char[]>(len + 1); |
| memcpy(out.get(), str, len); |
| out.get()[len] = 0; |
| pt->str = out.get(); |
| this->Variables.push_back(std::move(out)); |
| } |
| |
| bool cmCommandArgumentParserHelper::HandleEscapeSymbol( |
| cmCommandArgumentParserHelper::ParserType* pt, char symbol) |
| { |
| switch (symbol) { |
| case '\\': |
| case '"': |
| case ' ': |
| case '#': |
| case '(': |
| case ')': |
| case '$': |
| case '@': |
| case '^': |
| this->AllocateParserType(pt, &symbol, 1); |
| break; |
| case ';': |
| this->AllocateParserType(pt, "\\;", 2); |
| break; |
| case 't': |
| this->AllocateParserType(pt, "\t", 1); |
| break; |
| case 'n': |
| this->AllocateParserType(pt, "\n", 1); |
| break; |
| case 'r': |
| this->AllocateParserType(pt, "\r", 1); |
| break; |
| case '0': |
| this->AllocateParserType(pt, "\0", 1); |
| break; |
| default: { |
| std::ostringstream e; |
| e << "Invalid escape sequence \\" << symbol; |
| this->SetError(e.str()); |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| void cmCommandArgument_SetupEscapes(yyscan_t yyscanner, bool noEscapes); |
| |
| int cmCommandArgumentParserHelper::ParseString(std::string const& str, |
| int verb) |
| { |
| if (str.empty()) { |
| return 0; |
| } |
| this->InputSize = str.size(); |
| this->Verbose = verb; |
| |
| this->Result.clear(); |
| |
| yyscan_t yyscanner; |
| cmCommandArgument_yylex_init(&yyscanner); |
| auto* scanBuf = cmCommandArgument_yy_scan_string(str.c_str(), yyscanner); |
| cmCommandArgument_yyset_extra(this, yyscanner); |
| cmCommandArgument_SetupEscapes(yyscanner, this->NoEscapeMode); |
| int res = cmCommandArgument_yyparse(yyscanner); |
| cmCommandArgument_yy_delete_buffer(scanBuf, yyscanner); |
| cmCommandArgument_yylex_destroy(yyscanner); |
| if (res != 0) { |
| return 0; |
| } |
| |
| this->CleanupParser(); |
| |
| if (this->Verbose) { |
| std::cerr << "Expanding [" << str << "] produced: [" << this->Result << "]" |
| << std::endl; |
| } |
| return 1; |
| } |
| |
| void cmCommandArgumentParserHelper::CleanupParser() |
| { |
| this->Variables.clear(); |
| } |
| |
| void cmCommandArgumentParserHelper::Error(const char* str) |
| { |
| auto pos = this->InputBufferPos; |
| auto const isEof = (this->InputSize < this->InputBufferPos); |
| if (!isEof) { |
| pos -= this->LastTokenLength; |
| } |
| |
| std::ostringstream ostr; |
| ostr << str << " (" << pos << ")"; |
| this->SetError(ostr.str()); |
| } |
| |
| void cmCommandArgumentParserHelper::SetMakefile(const cmMakefile* mf) |
| { |
| this->Makefile = mf; |
| } |
| |
| void cmCommandArgumentParserHelper::SetResult(const char* value) |
| { |
| if (!value) { |
| this->Result.clear(); |
| return; |
| } |
| this->Result = value; |
| } |
| |
| void cmCommandArgumentParserHelper::SetError(std::string const& msg) |
| { |
| // Keep only the first error. |
| if (this->ErrorString.empty()) { |
| this->ErrorString = msg; |
| } |
| } |
| |
| void cmCommandArgumentParserHelper::UpdateInputPosition(int const tokenLength) |
| { |
| this->InputBufferPos += tokenLength; |
| this->LastTokenLength = tokenLength; |
| } |