CMake/Source/cmFortranParserImpl.cxx
Brad King 402735314e Fortran: Add support for submodule dependencies
Since commit v3.7.0-rc1~73^2~1 (Fortran: Add support for submodule
syntax in dependency scanning, 2016-09-05) we support parsing Fortran
sources that use submodule syntax, but it left addition of `.smod`
dependencies to future work.  Add it now.

The syntax

    submodule (module_name) submodule_name

means the current source requires `module_name.mod` and provides
`module_name@submodule_name.smod`.  The syntax

    submodule (module_name:submodule_name) nested_submodule_name

means the current source requires `module_name@submodule_name.smod`
provides `module_name@nested_submodule_name.smod`.

Fixes: #17017
2018-04-20 10:57:31 -04:00

417 lines
12 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmFortranParser.h"
#include "cmSystemTools.h"
#include <assert.h>
#include <set>
#include <stack>
#include <stdio.h>
#include <string>
#include <vector>
bool cmFortranParser_s::FindIncludeFile(const char* dir,
const char* includeName,
std::string& fileName)
{
// If the file is a full path, include it directly.
if (cmSystemTools::FileIsFullPath(includeName)) {
fileName = includeName;
return cmSystemTools::FileExists(fileName, true);
}
// Check for the file in the directory containing the including
// file.
std::string fullName = dir;
fullName += "/";
fullName += includeName;
if (cmSystemTools::FileExists(fullName, true)) {
fileName = fullName;
return true;
}
// Search the include path for the file.
for (std::string const& i : this->IncludePath) {
fullName = i;
fullName += "/";
fullName += includeName;
if (cmSystemTools::FileExists(fullName, true)) {
fileName = fullName;
return true;
}
}
return false;
}
cmFortranParser_s::cmFortranParser_s(std::vector<std::string> const& includes,
std::set<std::string> const& defines,
cmFortranSourceInfo& info)
: IncludePath(includes)
, PPDefinitions(defines)
, Info(info)
{
this->InInterface = false;
this->InPPFalseBranch = 0;
// Initialize the lexical scanner.
cmFortran_yylex_init(&this->Scanner);
cmFortran_yyset_extra(this, this->Scanner);
// Create a dummy buffer that is never read but is the fallback
// buffer when the last file is popped off the stack.
YY_BUFFER_STATE buffer =
cmFortran_yy_create_buffer(nullptr, 4, this->Scanner);
cmFortran_yy_switch_to_buffer(buffer, this->Scanner);
}
cmFortranParser_s::~cmFortranParser_s()
{
cmFortran_yylex_destroy(this->Scanner);
}
bool cmFortranParser_FilePush(cmFortranParser* parser, const char* fname)
{
// Open the new file and push it onto the stack. Save the old
// buffer with it on the stack.
if (FILE* file = cmsys::SystemTools::Fopen(fname, "rb")) {
YY_BUFFER_STATE current = cmFortranLexer_GetCurrentBuffer(parser->Scanner);
std::string dir = cmSystemTools::GetParentDirectory(fname);
cmFortranFile f(file, current, dir);
YY_BUFFER_STATE buffer =
cmFortran_yy_create_buffer(nullptr, 16384, parser->Scanner);
cmFortran_yy_switch_to_buffer(buffer, parser->Scanner);
parser->FileStack.push(f);
return true;
}
return false;
}
bool cmFortranParser_FilePop(cmFortranParser* parser)
{
// Pop one file off the stack and close it. Switch the lexer back
// to the next one on the stack.
if (parser->FileStack.empty()) {
return false;
}
cmFortranFile f = parser->FileStack.top();
parser->FileStack.pop();
fclose(f.File);
YY_BUFFER_STATE current = cmFortranLexer_GetCurrentBuffer(parser->Scanner);
cmFortran_yy_delete_buffer(current, parser->Scanner);
cmFortran_yy_switch_to_buffer(f.Buffer, parser->Scanner);
return true;
}
int cmFortranParser_Input(cmFortranParser* parser, char* buffer,
size_t bufferSize)
{
// Read from the file on top of the stack. If the stack is empty,
// the end of the translation unit has been reached.
if (!parser->FileStack.empty()) {
cmFortranFile& ff = parser->FileStack.top();
FILE* file = ff.File;
size_t n = fread(buffer, 1, bufferSize, file);
if (n > 0) {
ff.LastCharWasNewline = buffer[n - 1] == '\n';
} else if (!ff.LastCharWasNewline) {
// The file ended without a newline. Inject one so
// that the file always ends in an end-of-statement.
buffer[0] = '\n';
n = 1;
ff.LastCharWasNewline = true;
}
return static_cast<int>(n);
}
return 0;
}
void cmFortranParser_StringStart(cmFortranParser* parser)
{
parser->TokenString.clear();
}
const char* cmFortranParser_StringEnd(cmFortranParser* parser)
{
return parser->TokenString.c_str();
}
void cmFortranParser_StringAppend(cmFortranParser* parser, char c)
{
parser->TokenString += c;
}
void cmFortranParser_SetInInterface(cmFortranParser* parser, bool in)
{
if (parser->InPPFalseBranch) {
return;
}
parser->InInterface = in;
}
bool cmFortranParser_GetInInterface(cmFortranParser* parser)
{
return parser->InInterface;
}
void cmFortranParser_SetOldStartcond(cmFortranParser* parser, int arg)
{
parser->OldStartcond = arg;
}
int cmFortranParser_GetOldStartcond(cmFortranParser* parser)
{
return parser->OldStartcond;
}
void cmFortranParser_Error(cmFortranParser* parser, const char* msg)
{
parser->Error = msg ? msg : "unknown error";
}
void cmFortranParser_RuleUse(cmFortranParser* parser, const char* module_name)
{
if (parser->InPPFalseBranch) {
return;
}
// syntax: "use module_name"
// requires: "module_name.mod"
std::string const& mod_name = cmSystemTools::LowerCase(module_name);
parser->Info.Requires.insert(mod_name + ".mod");
}
void cmFortranParser_RuleLineDirective(cmFortranParser* parser,
const char* filename)
{
// This is a #line directive naming a file encountered during preprocessing.
std::string included = filename;
// Skip #line directives referencing non-files like
// "<built-in>" or "<command-line>".
if (included.empty() || included[0] == '<') {
return;
}
// Fix windows file path separators since our lexer does not
// process escape sequences in string literals.
cmSystemTools::ReplaceString(included, "\\\\", "\\");
cmSystemTools::ConvertToUnixSlashes(included);
// Save the named file as included in the source.
if (cmSystemTools::FileExists(included, true)) {
parser->Info.Includes.insert(included);
}
}
void cmFortranParser_RuleInclude(cmFortranParser* parser, const char* name)
{
if (parser->InPPFalseBranch) {
return;
}
// If processing an include statement there must be an open file.
assert(!parser->FileStack.empty());
// Get the directory containing the source in which the include
// statement appears. This is always the first search location for
// Fortran include files.
std::string dir = parser->FileStack.top().Directory;
// Find the included file. If it cannot be found just ignore the
// problem because either the source will not compile or the user
// does not care about depending on this included source.
std::string fullName;
if (parser->FindIncludeFile(dir.c_str(), name, fullName)) {
// Found the included file. Save it in the set of included files.
parser->Info.Includes.insert(fullName);
// Parse it immediately to translate the source inline.
cmFortranParser_FilePush(parser, fullName.c_str());
}
}
void cmFortranParser_RuleModule(cmFortranParser* parser,
const char* module_name)
{
if (parser->InPPFalseBranch) {
return;
}
if (!parser->InInterface) {
// syntax: "module module_name"
// provides: "module_name.mod"
std::string const& mod_name = cmSystemTools::LowerCase(module_name);
parser->Info.Provides.insert(mod_name + ".mod");
}
}
void cmFortranParser_RuleSubmodule(cmFortranParser* parser,
const char* module_name,
const char* submodule_name)
{
if (parser->InPPFalseBranch) {
return;
}
// syntax: "submodule (module_name) submodule_name"
// requires: "module_name.mod"
// provides: "module_name@submodule_name.smod"
//
// FIXME: Some compilers split the submodule part of a module into a
// separate "module_name.smod" file. Whether it is generated or
// not depends on conditions more subtle than we currently detect.
// For now we depend directly on "module_name.mod".
std::string const& mod_name = cmSystemTools::LowerCase(module_name);
std::string const& sub_name = cmSystemTools::LowerCase(submodule_name);
parser->Info.Requires.insert(mod_name + ".mod");
parser->Info.Provides.insert(mod_name + "@" + sub_name + ".smod");
}
void cmFortranParser_RuleSubmoduleNested(cmFortranParser* parser,
const char* module_name,
const char* submodule_name,
const char* nested_submodule_name)
{
if (parser->InPPFalseBranch) {
return;
}
// syntax: "submodule (module_name:submodule_name) nested_submodule_name"
// requires: "module_name@submodule_name.smod"
// provides: "module_name@nested_submodule_name.smod"
std::string const& mod_name = cmSystemTools::LowerCase(module_name);
std::string const& sub_name = cmSystemTools::LowerCase(submodule_name);
std::string const& nest_name =
cmSystemTools::LowerCase(nested_submodule_name);
parser->Info.Requires.insert(mod_name + "@" + sub_name + ".smod");
parser->Info.Provides.insert(mod_name + "@" + nest_name + ".smod");
}
void cmFortranParser_RuleDefine(cmFortranParser* parser, const char* macro)
{
if (!parser->InPPFalseBranch) {
parser->PPDefinitions.insert(macro);
}
}
void cmFortranParser_RuleUndef(cmFortranParser* parser, const char* macro)
{
if (!parser->InPPFalseBranch) {
std::set<std::string>::iterator match;
match = parser->PPDefinitions.find(macro);
if (match != parser->PPDefinitions.end()) {
parser->PPDefinitions.erase(match);
}
}
}
void cmFortranParser_RuleIfdef(cmFortranParser* parser, const char* macro)
{
// A new PP branch has been opened
parser->SkipToEnd.push(false);
if (parser->InPPFalseBranch) {
parser->InPPFalseBranch++;
} else if (parser->PPDefinitions.find(macro) ==
parser->PPDefinitions.end()) {
parser->InPPFalseBranch = 1;
} else {
parser->SkipToEnd.top() = true;
}
}
void cmFortranParser_RuleIfndef(cmFortranParser* parser, const char* macro)
{
// A new PP branch has been opened
parser->SkipToEnd.push(false);
if (parser->InPPFalseBranch) {
parser->InPPFalseBranch++;
} else if (parser->PPDefinitions.find(macro) !=
parser->PPDefinitions.end()) {
parser->InPPFalseBranch = 1;
} else {
// ignore other branches
parser->SkipToEnd.top() = true;
}
}
void cmFortranParser_RuleIf(cmFortranParser* parser)
{
/* Note: The current parser is _not_ able to get statements like
* #if 0
* #if 1
* #if MYSMBOL
* #if defined(MYSYMBOL)
* #if defined(MYSYMBOL) && ...
* right. The same for #elif. Thus in
* #if SYMBOL_1
* ..
* #elif SYMBOL_2
* ...
* ...
* #elif SYMBOL_N
* ..
* #else
* ..
* #endif
* _all_ N+1 branches are considered. If you got something like this
* #if defined(MYSYMBOL)
* #if !defined(MYSYMBOL)
* use
* #ifdef MYSYMBOL
* #ifndef MYSYMBOL
* instead.
*/
// A new PP branch has been opened
// Never skip! See note above.
parser->SkipToEnd.push(false);
}
void cmFortranParser_RuleElif(cmFortranParser* parser)
{
/* Note: There are parser limitations. See the note at
* cmFortranParser_RuleIf(..)
*/
// Always taken unless an #ifdef or #ifndef-branch has been taken
// already. If the second condition isn't meet already
// (parser->InPPFalseBranch == 0) correct it.
if (!parser->SkipToEnd.empty() && parser->SkipToEnd.top() &&
!parser->InPPFalseBranch) {
parser->InPPFalseBranch = 1;
}
}
void cmFortranParser_RuleElse(cmFortranParser* parser)
{
// if the parent branch is false do nothing!
if (parser->InPPFalseBranch > 1) {
return;
}
// parser->InPPFalseBranch is either 0 or 1. We change it depending on
// parser->SkipToEnd.top()
if (!parser->SkipToEnd.empty() && parser->SkipToEnd.top()) {
parser->InPPFalseBranch = 1;
} else {
parser->InPPFalseBranch = 0;
}
}
void cmFortranParser_RuleEndif(cmFortranParser* parser)
{
if (!parser->SkipToEnd.empty()) {
parser->SkipToEnd.pop();
}
// #endif doesn't know if there was a "#else" in before, so it
// always decreases InPPFalseBranch
if (parser->InPPFalseBranch) {
parser->InPPFalseBranch--;
}
}