mirror of
https://github.com/reactos/CMake.git
synced 2024-12-03 17:11:04 +00:00
daa0f6f98d
Teach the CMake language parser to recognize Lua-style "long bracket" arguments. These start with two '[' separated by zero or more '=' characters e.g. "[[" or "[=[" or "[==[". They end with two ']' separated by the same number of '=' as the opening bracket. There is no nesting of brackets of the same level (number of '='). No escapes, variable expansion, or other processing is performed on the content between such brackets so they always represent exactly one argument. Also teach CMake to parse and ignore "long comment" syntax. A long comment starts with "#" immediately followed by an opening long bracket. It ends at the matching close long bracket. Teach the RunCMake.Syntax test to cover long bracket and long comment cases.
424 lines
13 KiB
C++
424 lines
13 KiB
C++
/*============================================================================
|
|
CMake - Cross Platform Makefile Generator
|
|
Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
|
|
|
|
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 "cmListFileCache.h"
|
|
|
|
#include "cmListFileLexer.h"
|
|
#include "cmSystemTools.h"
|
|
#include "cmMakefile.h"
|
|
#include "cmVersion.h"
|
|
|
|
#include <cmsys/RegularExpression.hxx>
|
|
|
|
#ifdef __BORLANDC__
|
|
# pragma warn -8060 /* possibly incorrect assignment */
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------------
|
|
struct cmListFileParser
|
|
{
|
|
cmListFileParser(cmListFile* lf, cmMakefile* mf, const char* filename);
|
|
~cmListFileParser();
|
|
bool ParseFile();
|
|
bool ParseFunction(const char* name, long line);
|
|
bool AddArgument(cmListFileLexer_Token* token,
|
|
cmListFileArgument::Delimiter delim);
|
|
cmListFile* ListFile;
|
|
cmMakefile* Makefile;
|
|
const char* FileName;
|
|
cmListFileLexer* Lexer;
|
|
cmListFileFunction Function;
|
|
enum { SeparationOkay, SeparationWarning, SeparationError} Separation;
|
|
};
|
|
|
|
//----------------------------------------------------------------------------
|
|
cmListFileParser::cmListFileParser(cmListFile* lf, cmMakefile* mf,
|
|
const char* filename):
|
|
ListFile(lf), Makefile(mf), FileName(filename),
|
|
Lexer(cmListFileLexer_New())
|
|
{
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
cmListFileParser::~cmListFileParser()
|
|
{
|
|
cmListFileLexer_Delete(this->Lexer);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool cmListFileParser::ParseFile()
|
|
{
|
|
// Open the file.
|
|
cmListFileLexer_BOM bom;
|
|
if(!cmListFileLexer_SetFileName(this->Lexer, this->FileName, &bom))
|
|
{
|
|
cmSystemTools::Error("cmListFileCache: error can not open file ",
|
|
this->FileName);
|
|
return false;
|
|
}
|
|
|
|
// Verify the Byte-Order-Mark, if any.
|
|
if(bom != cmListFileLexer_BOM_None &&
|
|
bom != cmListFileLexer_BOM_UTF8)
|
|
{
|
|
cmListFileLexer_SetFileName(this->Lexer, 0, 0);
|
|
cmOStringStream m;
|
|
m << "File\n " << this->FileName << "\n"
|
|
<< "starts with a Byte-Order-Mark that is not UTF-8.";
|
|
this->Makefile->IssueMessage(cmake::FATAL_ERROR, m.str());
|
|
return false;
|
|
}
|
|
|
|
// Use a simple recursive-descent parser to process the token
|
|
// stream.
|
|
bool haveNewline = true;
|
|
while(cmListFileLexer_Token* token =
|
|
cmListFileLexer_Scan(this->Lexer))
|
|
{
|
|
if(token->type == cmListFileLexer_Token_Space)
|
|
{
|
|
}
|
|
else if(token->type == cmListFileLexer_Token_Newline)
|
|
{
|
|
haveNewline = true;
|
|
}
|
|
else if(token->type == cmListFileLexer_Token_CommentBracket)
|
|
{
|
|
haveNewline = false;
|
|
}
|
|
else if(token->type == cmListFileLexer_Token_Identifier)
|
|
{
|
|
if(haveNewline)
|
|
{
|
|
haveNewline = false;
|
|
if(this->ParseFunction(token->text, token->line))
|
|
{
|
|
this->ListFile->Functions.push_back(this->Function);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cmOStringStream error;
|
|
error << "Error in cmake code at\n"
|
|
<< this->FileName << ":" << token->line << ":\n"
|
|
<< "Parse error. Expected a newline, got "
|
|
<< cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
|
|
<< " with text \"" << token->text << "\".";
|
|
cmSystemTools::Error(error.str().c_str());
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cmOStringStream error;
|
|
error << "Error in cmake code at\n"
|
|
<< this->FileName << ":" << token->line << ":\n"
|
|
<< "Parse error. Expected a command name, got "
|
|
<< cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
|
|
<< " with text \""
|
|
<< token->text << "\".";
|
|
cmSystemTools::Error(error.str().c_str());
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool cmListFile::ParseFile(const char* filename,
|
|
bool topLevel,
|
|
cmMakefile *mf)
|
|
{
|
|
if(!cmSystemTools::FileExists(filename))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool parseError = false;
|
|
this->ModifiedTime = cmSystemTools::ModifiedTime(filename);
|
|
|
|
{
|
|
cmListFileParser parser(this, mf, filename);
|
|
parseError = !parser.ParseFile();
|
|
}
|
|
|
|
if(parseError)
|
|
{
|
|
this->ModifiedTime = 0;
|
|
}
|
|
|
|
// do we need a cmake_policy(VERSION call?
|
|
if(topLevel)
|
|
{
|
|
bool hasVersion = false;
|
|
// search for the right policy command
|
|
for(std::vector<cmListFileFunction>::iterator i
|
|
= this->Functions.begin();
|
|
i != this->Functions.end(); ++i)
|
|
{
|
|
if (cmSystemTools::LowerCase(i->Name) == "cmake_minimum_required")
|
|
{
|
|
hasVersion = true;
|
|
break;
|
|
}
|
|
}
|
|
// if no policy command is found this is an error if they use any
|
|
// non advanced functions or a lot of functions
|
|
if(!hasVersion)
|
|
{
|
|
bool isProblem = true;
|
|
if (this->Functions.size() < 30)
|
|
{
|
|
// the list of simple commands DO NOT ADD TO THIS LIST!!!!!
|
|
// these commands must have backwards compatibility forever and
|
|
// and that is a lot longer than your tiny mind can comprehend mortal
|
|
std::set<std::string> allowedCommands;
|
|
allowedCommands.insert("project");
|
|
allowedCommands.insert("set");
|
|
allowedCommands.insert("if");
|
|
allowedCommands.insert("endif");
|
|
allowedCommands.insert("else");
|
|
allowedCommands.insert("elseif");
|
|
allowedCommands.insert("add_executable");
|
|
allowedCommands.insert("add_library");
|
|
allowedCommands.insert("target_link_libraries");
|
|
allowedCommands.insert("option");
|
|
allowedCommands.insert("message");
|
|
isProblem = false;
|
|
for(std::vector<cmListFileFunction>::iterator i
|
|
= this->Functions.begin();
|
|
i != this->Functions.end(); ++i)
|
|
{
|
|
std::string name = cmSystemTools::LowerCase(i->Name);
|
|
if (allowedCommands.find(name) == allowedCommands.end())
|
|
{
|
|
isProblem = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isProblem)
|
|
{
|
|
// Tell the top level cmMakefile to diagnose
|
|
// this violation of CMP0000.
|
|
mf->SetCheckCMP0000(true);
|
|
|
|
// Implicitly set the version for the user.
|
|
mf->SetPolicyVersion("2.4");
|
|
}
|
|
}
|
|
}
|
|
|
|
if(topLevel)
|
|
{
|
|
bool hasProject = false;
|
|
// search for a project command
|
|
for(std::vector<cmListFileFunction>::iterator i
|
|
= this->Functions.begin();
|
|
i != this->Functions.end(); ++i)
|
|
{
|
|
if(cmSystemTools::LowerCase(i->Name) == "project")
|
|
{
|
|
hasProject = true;
|
|
break;
|
|
}
|
|
}
|
|
// if no project command is found, add one
|
|
if(!hasProject)
|
|
{
|
|
cmListFileFunction project;
|
|
project.Name = "PROJECT";
|
|
cmListFileArgument prj("Project", cmListFileArgument::Unquoted,
|
|
filename, 0);
|
|
project.Arguments.push_back(prj);
|
|
this->Functions.insert(this->Functions.begin(),project);
|
|
}
|
|
}
|
|
if(parseError)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool cmListFileParser::ParseFunction(const char* name, long line)
|
|
{
|
|
// Inintialize a new function call.
|
|
this->Function = cmListFileFunction();
|
|
this->Function.FilePath = this->FileName;
|
|
this->Function.Name = name;
|
|
this->Function.Line = line;
|
|
|
|
// Command name has already been parsed. Read the left paren.
|
|
cmListFileLexer_Token* token;
|
|
while((token = cmListFileLexer_Scan(this->Lexer)) &&
|
|
token->type == cmListFileLexer_Token_Space) {}
|
|
if(!token)
|
|
{
|
|
cmOStringStream error;
|
|
error << "Error in cmake code at\n" << this->FileName << ":"
|
|
<< cmListFileLexer_GetCurrentLine(this->Lexer) << ":\n"
|
|
<< "Parse error. Function missing opening \"(\".";
|
|
cmSystemTools::Error(error.str().c_str());
|
|
return false;
|
|
}
|
|
if(token->type != cmListFileLexer_Token_ParenLeft)
|
|
{
|
|
cmOStringStream error;
|
|
error << "Error in cmake code at\n" << this->FileName << ":"
|
|
<< cmListFileLexer_GetCurrentLine(this->Lexer) << ":\n"
|
|
<< "Parse error. Expected \"(\", got "
|
|
<< cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
|
|
<< " with text \"" << token->text << "\".";
|
|
cmSystemTools::Error(error.str().c_str());
|
|
return false;
|
|
}
|
|
|
|
// Arguments.
|
|
unsigned long lastLine;
|
|
unsigned long parenDepth = 0;
|
|
this->Separation = SeparationOkay;
|
|
while((lastLine = cmListFileLexer_GetCurrentLine(this->Lexer),
|
|
token = cmListFileLexer_Scan(this->Lexer)))
|
|
{
|
|
if(token->type == cmListFileLexer_Token_Space ||
|
|
token->type == cmListFileLexer_Token_Newline)
|
|
{
|
|
this->Separation = SeparationOkay;
|
|
continue;
|
|
}
|
|
if(token->type == cmListFileLexer_Token_ParenLeft)
|
|
{
|
|
parenDepth++;
|
|
this->Separation = SeparationOkay;
|
|
if(!this->AddArgument(token, cmListFileArgument::Unquoted))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else if(token->type == cmListFileLexer_Token_ParenRight)
|
|
{
|
|
if (parenDepth == 0)
|
|
{
|
|
return true;
|
|
}
|
|
parenDepth--;
|
|
this->Separation = SeparationOkay;
|
|
if(!this->AddArgument(token, cmListFileArgument::Unquoted))
|
|
{
|
|
return false;
|
|
}
|
|
this->Separation = SeparationWarning;
|
|
}
|
|
else if(token->type == cmListFileLexer_Token_Identifier ||
|
|
token->type == cmListFileLexer_Token_ArgumentUnquoted)
|
|
{
|
|
if(!this->AddArgument(token, cmListFileArgument::Unquoted))
|
|
{
|
|
return false;
|
|
}
|
|
this->Separation = SeparationWarning;
|
|
}
|
|
else if(token->type == cmListFileLexer_Token_ArgumentQuoted)
|
|
{
|
|
if(!this->AddArgument(token, cmListFileArgument::Quoted))
|
|
{
|
|
return false;
|
|
}
|
|
this->Separation = SeparationWarning;
|
|
}
|
|
else if(token->type == cmListFileLexer_Token_ArgumentBracket)
|
|
{
|
|
if(!this->AddArgument(token, cmListFileArgument::Bracket))
|
|
{
|
|
return false;
|
|
}
|
|
this->Separation = SeparationError;
|
|
}
|
|
else if(token->type == cmListFileLexer_Token_CommentBracket)
|
|
{
|
|
this->Separation = SeparationError;
|
|
}
|
|
else
|
|
{
|
|
// Error.
|
|
cmOStringStream error;
|
|
error << "Error in cmake code at\n" << this->FileName << ":"
|
|
<< cmListFileLexer_GetCurrentLine(this->Lexer) << ":\n"
|
|
<< "Parse error. Function missing ending \")\". "
|
|
<< "Instead found "
|
|
<< cmListFileLexer_GetTypeAsString(this->Lexer, token->type)
|
|
<< " with text \"" << token->text << "\".";
|
|
cmSystemTools::Error(error.str().c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
cmOStringStream error;
|
|
error << "Error in cmake code at\n"
|
|
<< this->FileName << ":" << lastLine << ":\n"
|
|
<< "Parse error. Function missing ending \")\". "
|
|
<< "End of file reached.";
|
|
cmSystemTools::Error(error.str().c_str());
|
|
|
|
return false;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool cmListFileParser::AddArgument(cmListFileLexer_Token* token,
|
|
cmListFileArgument::Delimiter delim)
|
|
{
|
|
cmListFileArgument a(token->text, delim, this->FileName, token->line);
|
|
this->Function.Arguments.push_back(a);
|
|
if(this->Separation == SeparationOkay)
|
|
{
|
|
return true;
|
|
}
|
|
bool isError = (this->Separation == SeparationError ||
|
|
delim == cmListFileArgument::Bracket);
|
|
cmOStringStream m;
|
|
m << "Syntax " << (isError? "Error":"Warning") << " in cmake code at\n"
|
|
<< " " << this->FileName << ":" << token->line << ":"
|
|
<< token->column << "\n"
|
|
<< "Argument not separated from preceding token by whitespace.";
|
|
if(isError)
|
|
{
|
|
this->Makefile->IssueMessage(cmake::FATAL_ERROR, m.str().c_str());
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
this->Makefile->IssueMessage(cmake::AUTHOR_WARNING, m.str().c_str());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
std::ostream& operator<<(std::ostream& os, cmListFileContext const& lfc)
|
|
{
|
|
os << lfc.FilePath;
|
|
if(lfc.Line)
|
|
{
|
|
os << ":" << lfc.Line;
|
|
if(!lfc.Name.empty())
|
|
{
|
|
os << " (" << lfc.Name << ")";
|
|
}
|
|
}
|
|
return os;
|
|
}
|