1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #define cmListFileCache_cxx
4 #include "cmListFileCache.h"
11 # include <cmsys/Encoding.hxx>
14 #include "cmListFileLexer.h"
15 #include "cmMessageType.h"
16 #include "cmMessenger.h"
17 #include "cmStringAlgorithms.h"
18 #include "cmSystemTools.h"
20 struct cmListFileParser
22 cmListFileParser(cmListFile* lf, cmListFileBacktrace lfbt,
23 cmMessenger* messenger);
25 cmListFileParser(const cmListFileParser&) = delete;
26 cmListFileParser& operator=(const cmListFileParser&) = delete;
27 void IssueFileOpenError(std::string const& text) const;
28 void IssueError(std::string const& text) const;
29 bool ParseFile(const char* filename);
30 bool ParseString(const char* str, const char* virtual_filename);
32 bool ParseFunction(const char* name, long line);
33 bool AddArgument(cmListFileLexer_Token* token,
34 cmListFileArgument::Delimiter delim);
35 cm::optional<cmListFileContext> CheckNesting() const;
37 cmListFileBacktrace Backtrace;
38 cmMessenger* Messenger;
39 const char* FileName = nullptr;
40 cmListFileLexer* Lexer;
41 std::string FunctionName;
44 std::vector<cmListFileArgument> FunctionArguments;
53 cmListFileParser::cmListFileParser(cmListFile* lf, cmListFileBacktrace lfbt,
54 cmMessenger* messenger)
56 , Backtrace(std::move(lfbt))
57 , Messenger(messenger)
58 , Lexer(cmListFileLexer_New())
62 cmListFileParser::~cmListFileParser()
64 cmListFileLexer_Delete(this->Lexer);
67 void cmListFileParser::IssueFileOpenError(const std::string& text) const
69 this->Messenger->IssueMessage(MessageType::FATAL_ERROR, text,
73 void cmListFileParser::IssueError(const std::string& text) const
75 cmListFileContext lfc;
76 lfc.FilePath = this->FileName;
77 lfc.Line = cmListFileLexer_GetCurrentLine(this->Lexer);
78 cmListFileBacktrace lfbt = this->Backtrace;
79 lfbt = lfbt.Push(lfc);
80 this->Messenger->IssueMessage(MessageType::FATAL_ERROR, text, lfbt);
81 cmSystemTools::SetFatalErrorOccurred();
84 bool cmListFileParser::ParseFile(const char* filename)
86 this->FileName = filename;
89 std::string expandedFileName = cmsys::Encoding::ToNarrow(
90 cmSystemTools::ConvertToWindowsExtendedPath(filename));
91 filename = expandedFileName.c_str();
95 cmListFileLexer_BOM bom;
96 if (!cmListFileLexer_SetFileName(this->Lexer, filename, &bom)) {
97 this->IssueFileOpenError("cmListFileCache: error can not open file.");
101 if (bom == cmListFileLexer_BOM_Broken) {
102 cmListFileLexer_SetFileName(this->Lexer, nullptr, nullptr);
103 this->IssueFileOpenError("Error while reading Byte-Order-Mark. "
104 "File not seekable?");
108 // Verify the Byte-Order-Mark, if any.
109 if (bom != cmListFileLexer_BOM_None && bom != cmListFileLexer_BOM_UTF8) {
110 cmListFileLexer_SetFileName(this->Lexer, nullptr, nullptr);
111 this->IssueFileOpenError(
112 "File starts with a Byte-Order-Mark that is not UTF-8.");
116 return this->Parse();
119 bool cmListFileParser::ParseString(const char* str,
120 const char* virtual_filename)
122 this->FileName = virtual_filename;
124 if (!cmListFileLexer_SetString(this->Lexer, str)) {
125 this->IssueFileOpenError("cmListFileCache: cannot allocate buffer.");
129 return this->Parse();
132 bool cmListFileParser::Parse()
134 // Use a simple recursive-descent parser to process the token
136 bool haveNewline = true;
137 while (cmListFileLexer_Token* token = cmListFileLexer_Scan(this->Lexer)) {
138 if (token->type == cmListFileLexer_Token_Space) {
139 } else if (token->type == cmListFileLexer_Token_Newline) {
141 } else if (token->type == cmListFileLexer_Token_CommentBracket) {
143 } else if (token->type == cmListFileLexer_Token_Identifier) {
146 if (this->ParseFunction(token->text, token->line)) {
147 this->ListFile->Functions.emplace_back(
148 std::move(this->FunctionName), this->FunctionLine,
149 this->FunctionLineEnd, std::move(this->FunctionArguments));
154 std::ostringstream error;
155 error << "Parse error. Expected a newline, got "
156 << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
157 << " with text \"" << token->text << "\".";
158 this->IssueError(error.str());
162 std::ostringstream error;
163 error << "Parse error. Expected a command name, got "
164 << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
165 << " with text \"" << token->text << "\".";
166 this->IssueError(error.str());
171 // Check if all functions are nested properly.
172 if (auto badNesting = this->CheckNesting()) {
173 this->Messenger->IssueMessage(
174 MessageType::FATAL_ERROR,
175 "Flow control statements are not properly nested.",
176 this->Backtrace.Push(*badNesting));
177 cmSystemTools::SetFatalErrorOccurred();
184 bool cmListFile::ParseFile(const char* filename, cmMessenger* messenger,
185 cmListFileBacktrace const& lfbt)
187 if (!cmSystemTools::FileExists(filename) ||
188 cmSystemTools::FileIsDirectory(filename)) {
192 bool parseError = false;
195 cmListFileParser parser(this, lfbt, messenger);
196 parseError = !parser.ParseFile(filename);
202 bool cmListFile::ParseString(const char* str, const char* virtual_filename,
203 cmMessenger* messenger,
204 const cmListFileBacktrace& lfbt)
206 bool parseError = false;
209 cmListFileParser parser(this, lfbt, messenger);
210 parseError = !parser.ParseString(str, virtual_filename);
216 bool cmListFileParser::ParseFunction(const char* name, long line)
218 // Ininitialize a new function call.
219 this->FunctionName = name;
220 this->FunctionLine = line;
222 // Command name has already been parsed. Read the left paren.
223 cmListFileLexer_Token* token;
224 while ((token = cmListFileLexer_Scan(this->Lexer)) &&
225 token->type == cmListFileLexer_Token_Space) {
228 std::ostringstream error;
229 /* clang-format off */
230 error << "Unexpected end of file.\n"
231 << "Parse error. Function missing opening \"(\".";
232 /* clang-format on */
233 this->IssueError(error.str());
236 if (token->type != cmListFileLexer_Token_ParenLeft) {
237 std::ostringstream error;
238 error << "Parse error. Expected \"(\", got "
239 << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
240 << " with text \"" << token->text << "\".";
241 this->IssueError(error.str());
246 unsigned long parenDepth = 0;
247 this->Separation = SeparationOkay;
248 while ((token = cmListFileLexer_Scan(this->Lexer))) {
249 if (token->type == cmListFileLexer_Token_Space ||
250 token->type == cmListFileLexer_Token_Newline) {
251 this->Separation = SeparationOkay;
254 if (token->type == cmListFileLexer_Token_ParenLeft) {
256 this->Separation = SeparationOkay;
257 if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
260 } else if (token->type == cmListFileLexer_Token_ParenRight) {
261 if (parenDepth == 0) {
262 this->FunctionLineEnd = token->line;
266 this->Separation = SeparationOkay;
267 if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
270 this->Separation = SeparationWarning;
271 } else if (token->type == cmListFileLexer_Token_Identifier ||
272 token->type == cmListFileLexer_Token_ArgumentUnquoted) {
273 if (!this->AddArgument(token, cmListFileArgument::Unquoted)) {
276 this->Separation = SeparationWarning;
277 } else if (token->type == cmListFileLexer_Token_ArgumentQuoted) {
278 if (!this->AddArgument(token, cmListFileArgument::Quoted)) {
281 this->Separation = SeparationWarning;
282 } else if (token->type == cmListFileLexer_Token_ArgumentBracket) {
283 if (!this->AddArgument(token, cmListFileArgument::Bracket)) {
286 this->Separation = SeparationError;
287 } else if (token->type == cmListFileLexer_Token_CommentBracket) {
288 this->Separation = SeparationError;
291 std::ostringstream error;
292 error << "Parse error. Function missing ending \")\". "
294 << cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
295 << " with text \"" << token->text << "\".";
296 this->IssueError(error.str());
301 std::ostringstream error;
302 cmListFileContext lfc;
303 lfc.FilePath = this->FileName;
305 cmListFileBacktrace lfbt = this->Backtrace;
306 lfbt = lfbt.Push(lfc);
307 error << "Parse error. Function missing ending \")\". "
308 << "End of file reached.";
309 this->Messenger->IssueMessage(MessageType::FATAL_ERROR, error.str(), lfbt);
313 bool cmListFileParser::AddArgument(cmListFileLexer_Token* token,
314 cmListFileArgument::Delimiter delim)
316 this->FunctionArguments.emplace_back(token->text, delim, token->line);
317 if (this->Separation == SeparationOkay) {
320 bool isError = (this->Separation == SeparationError ||
321 delim == cmListFileArgument::Bracket);
322 std::ostringstream m;
323 cmListFileContext lfc;
324 lfc.FilePath = this->FileName;
325 lfc.Line = token->line;
326 cmListFileBacktrace lfbt = this->Backtrace;
327 lfbt = lfbt.Push(lfc);
329 m << "Syntax " << (isError ? "Error" : "Warning") << " in cmake code at "
330 << "column " << token->column << "\n"
331 << "Argument not separated from preceding token by whitespace.";
332 /* clang-format on */
334 this->Messenger->IssueMessage(MessageType::FATAL_ERROR, m.str(), lfbt);
337 this->Messenger->IssueMessage(MessageType::AUTHOR_WARNING, m.str(), lfbt);
342 enum class NestingStateEnum
355 NestingStateEnum State;
356 cmListFileContext Context;
359 bool TopIs(std::vector<NestingState>& stack, NestingStateEnum state)
361 return !stack.empty() && stack.back().State == state;
365 cm::optional<cmListFileContext> cmListFileParser::CheckNesting() const
367 std::vector<NestingState> stack;
369 for (auto const& func : this->ListFile->Functions) {
370 auto const& name = func.LowerCaseName();
373 NestingStateEnum::If,
374 cmListFileContext::FromListFileFunction(func, this->FileName),
376 } else if (name == "elseif") {
377 if (!TopIs(stack, NestingStateEnum::If)) {
378 return cmListFileContext::FromListFileFunction(func, this->FileName);
381 NestingStateEnum::If,
382 cmListFileContext::FromListFileFunction(func, this->FileName),
384 } else if (name == "else") {
385 if (!TopIs(stack, NestingStateEnum::If)) {
386 return cmListFileContext::FromListFileFunction(func, this->FileName);
389 NestingStateEnum::Else,
390 cmListFileContext::FromListFileFunction(func, this->FileName),
392 } else if (name == "endif") {
393 if (!TopIs(stack, NestingStateEnum::If) &&
394 !TopIs(stack, NestingStateEnum::Else)) {
395 return cmListFileContext::FromListFileFunction(func, this->FileName);
398 } else if (name == "while") {
400 NestingStateEnum::While,
401 cmListFileContext::FromListFileFunction(func, this->FileName),
403 } else if (name == "endwhile") {
404 if (!TopIs(stack, NestingStateEnum::While)) {
405 return cmListFileContext::FromListFileFunction(func, this->FileName);
408 } else if (name == "foreach") {
410 NestingStateEnum::Foreach,
411 cmListFileContext::FromListFileFunction(func, this->FileName),
413 } else if (name == "endforeach") {
414 if (!TopIs(stack, NestingStateEnum::Foreach)) {
415 return cmListFileContext::FromListFileFunction(func, this->FileName);
418 } else if (name == "function") {
420 NestingStateEnum::Function,
421 cmListFileContext::FromListFileFunction(func, this->FileName),
423 } else if (name == "endfunction") {
424 if (!TopIs(stack, NestingStateEnum::Function)) {
425 return cmListFileContext::FromListFileFunction(func, this->FileName);
428 } else if (name == "macro") {
430 NestingStateEnum::Macro,
431 cmListFileContext::FromListFileFunction(func, this->FileName),
433 } else if (name == "endmacro") {
434 if (!TopIs(stack, NestingStateEnum::Macro)) {
435 return cmListFileContext::FromListFileFunction(func, this->FileName);
438 } else if (name == "block") {
440 NestingStateEnum::Block,
441 cmListFileContext::FromListFileFunction(func, this->FileName),
443 } else if (name == "endblock") {
444 if (!TopIs(stack, NestingStateEnum::Block)) {
445 return cmListFileContext::FromListFileFunction(func, this->FileName);
451 if (!stack.empty()) {
452 return stack.back().Context;
458 #include "cmConstStack.tcc"
459 template class cmConstStack<cmListFileContext, cmListFileBacktrace>;
461 std::ostream& operator<<(std::ostream& os, cmListFileContext const& lfc)
465 os << ":" << lfc.Line;
466 if (!lfc.Name.empty()) {
467 os << " (" << lfc.Name << ")";
469 } else if (lfc.Line == cmListFileContext::DeferPlaceholderLine) {
475 bool operator<(const cmListFileContext& lhs, const cmListFileContext& rhs)
477 if (lhs.Line != rhs.Line) {
478 return lhs.Line < rhs.Line;
480 return lhs.FilePath < rhs.FilePath;
483 bool operator==(const cmListFileContext& lhs, const cmListFileContext& rhs)
485 return lhs.Line == rhs.Line && lhs.FilePath == rhs.FilePath;
488 bool operator!=(const cmListFileContext& lhs, const cmListFileContext& rhs)
490 return !(lhs == rhs);
493 std::ostream& operator<<(std::ostream& os, BT<std::string> const& s)
495 return os << s.Value;
498 std::vector<BT<std::string>> cmExpandListWithBacktrace(
499 std::string const& list, cmListFileBacktrace const& bt, bool emptyArgs)
501 std::vector<BT<std::string>> result;
502 std::vector<std::string> tmp = cmExpandedList(list, emptyArgs);
503 result.reserve(tmp.size());
504 for (std::string& i : tmp) {
505 result.emplace_back(std::move(i), bt);