files. `-module` specifics a directory to write module files in as well as to
search for them. gfortran is similar except it uses `-J` instead of `-module`.
+The search order for module files is:
+1. The `-module` directory (Note: for gfortran the `-J` directory is not searched).
+2. The current directory
+3. The `-I` directories in the order they appear on the command line
+
### Writing module files
When writing a module file, if the existing one matches what would be written,
#include "mod-file.h"
#include "scope.h"
#include "symbol.h"
+#include "../parser/grammar.h"
#include "../parser/message.h"
+#include "../parser/openmp-grammar.h"
+#include "../parser/preprocessor.h"
+#include "../parser/prescan.h"
#include <algorithm>
#include <cerrno>
#include <fstream>
#include <ostream>
#include <set>
#include <sstream>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <vector>
namespace Fortran::semantics {
using namespace parser::literals;
+// The extension used for module files.
+static constexpr auto extension{".mod"};
+// The initial characters of a file that identify it as a .mod file.
+static constexpr auto magic{"!mod$"};
+// Construct the path to a module file.
+static std::string ModFilePath(const std::string &, const std::string &);
+// Helpers for creating error messages.
+static parser::Message Error(
+ const SourceName &, parser::MessageFixedText, const std::string &);
+static parser::Message Error(const SourceName &, parser::MessageFixedText,
+ const std::string &, const std::string &);
+
class ModFileWriter {
public:
- // The initial characters of a file that identify it as a .mod file.
- static constexpr auto magic{"!mod$"};
- static constexpr auto extension{".mod"};
-
// The .mod file format version number.
void set_version(int version) { version_ = version; }
// The directory to write .mod files in.
for (const auto &scope : Scope::globalScope.children()) {
if (scope.kind() == Scope::Kind::Module) {
auto &symbol{*scope.symbol()}; // symbol must be present for module
- WriteOne(symbol);
+ if (!symbol.test(Symbol::Flag::ModFile)) {
+ WriteOne(symbol);
+ }
}
}
return errors_.empty();
bool ModFileWriter::WriteOne(const Symbol &modSymbol) {
CHECK(modSymbol.has<ModuleDetails>());
auto name{parser::ToLowerCaseLetters(modSymbol.name().ToString())};
- std::string path{dir_ + '/' + name + extension};
+ std::string path{ModFilePath(dir_, name)};
std::ofstream os{path};
PutSymbols(*modSymbol.scope());
std::string all{GetAsString(name)};
void WriteModFiles() { ModFileWriter{}.WriteAll(std::cerr); }
+bool ModFileReader::Read(const SourceName &modName) {
+ auto path{FindModFile(modName)};
+ if (!path.has_value()) {
+ return false;
+ }
+ if (!Prescan(modName, *path)) {
+ return false;
+ }
+ parser::ParseState parseState{*cooked_};
+ auto parseTree{parser::program.Parse(parseState)};
+ if (!parseState.messages().empty()) {
+ errors_.emplace_back(modName,
+ parser::MessageFormattedText{
+ "Module file for '%s' is corrupt: %s"_err_en_US,
+ modName.ToString().data(), path->data()});
+ return false;
+ }
+ ResolveNames(*parseTree, *cooked_, directories_);
+ const auto &it{Scope::globalScope.find(modName)};
+ if (it == Scope::globalScope.end()) {
+ return false;
+ }
+ auto &modSymbol{*it->second};
+ modSymbol.scope()->set_cookedSource(std::move(cooked_));
+ modSymbol.set(Symbol::Flag::ModFile);
+ return true;
+}
+
+// Look for the .mod file for this module in the search directories.
+// Add to errors_ if not found.
+std::optional<std::string> ModFileReader::FindModFile(
+ const SourceName &modName) {
+ auto error{Error(modName, "Cannot find module file for '%s'"_err_en_US,
+ modName.ToString())};
+ for (auto &dir : directories_) {
+ std::string path{ModFilePath(dir, modName.ToString())};
+ std::ifstream ifstream{path};
+ if (!ifstream.good()) {
+ error.Attach(Error(
+ modName, "%s: %s"_en_US, path, std::string{std::strerror(errno)}));
+ } else {
+ std::string line;
+ ifstream >> line;
+ if (std::equal(line.begin(), line.end(), std::string{magic}.begin())) {
+ return path; // success
+ }
+ error.Attach(Error(modName, "%s: Not a valid module file"_en_US, path));
+ }
+ }
+ errors_.push_back(error);
+ return std::nullopt;
+}
+
+bool ModFileReader::Prescan(
+ const SourceName &modName, const std::string &path) {
+ std::stringstream fileError;
+ const auto *sourceFile{allSources_.Open(path, &fileError)};
+ if (sourceFile == nullptr) {
+ errors_.push_back(
+ Error(modName, "Cannot read %s: %s"_err_en_US, path, fileError.str()));
+ return false;
+ }
+ parser::Preprocessor preprocessor{allSources_};
+ parser::Messages messages;
+ parser::Prescanner prescanner{messages, *cooked_, preprocessor, {}};
+ parser::ProvenanceRange range{
+ allSources_.AddIncludedFile(*sourceFile, parser::ProvenanceRange{})};
+ prescanner.Prescan(range);
+ if (!messages.empty()) {
+ errors_.push_back(
+ Error(modName, "Module file for '%s' is corrupt: %s"_err_en_US,
+ modName.ToString(), path));
+ return false;
+ }
+ cooked_->Marshal();
+ return true;
+}
+
+static std::string ModFilePath(
+ const std::string &dir, const std::string &modName) {
+ if (dir == "."s) {
+ return modName + extension;
+ } else {
+ return dir + '/' + modName + extension;
+ }
+}
+
+static parser::Message Error(const SourceName &location,
+ parser::MessageFixedText fixedText, const std::string &arg) {
+ return parser::Message{
+ location, parser::MessageFormattedText{fixedText, arg.data()}};
+}
+static parser::Message Error(const SourceName &location,
+ parser::MessageFixedText fixedText, const std::string &arg1,
+ const std::string &arg2) {
+ return parser::Message{location,
+ parser::MessageFormattedText{fixedText, arg1.data(), arg2.data()}};
+}
+
} // namespace Fortran::semantics
#ifndef FORTRAN_SEMANTICS_MOD_FILE_H_
#define FORTRAN_SEMANTICS_MOD_FILE_H_
+#include "resolve-names.h"
+#include "../parser/char-block.h"
+#include "../parser/message.h"
+#include "../parser/parse-tree.h"
+#include "../parser/parsing.h"
+#include "../parser/provenance.h"
+#include <iostream>
+#include <string>
+
namespace Fortran::semantics {
+using SourceName = parser::CharBlock;
+
void WriteModFiles();
+class ModFileReader {
+public:
+ // directories specifies where to search for module files
+ ModFileReader(const std::vector<std::string> &directories)
+ : directories_{directories} {}
+
+ // Find and read the module file for modName.
+ // Return true on success; otherwise errors() reports the problems.
+ bool Read(const SourceName &modName);
+ std::list<parser::Message> &errors() { return errors_; }
+
+private:
+ std::vector<std::string> directories_;
+ parser::AllSources allSources_;
+ std::unique_ptr<parser::CookedSource> cooked_{
+ std::make_unique<parser::CookedSource>(allSources_)};
+ std::list<parser::Message> errors_;
+
+ std::optional<std::string> FindModFile(const SourceName &);
+ bool Prescan(const SourceName &, const std::string &);
+};
+
} // namespace Fortran::semantics
#endif
#include <ostream>
#include <set>
#include <stack>
+#include <vector>
namespace Fortran::semantics {
bool Pre(const parser::UseStmt &);
void Post(const parser::UseStmt &);
+ void add_searchDirectory(const std::string &dir) {
+ searchDirectories_.push_back(dir);
+ }
+
private:
// The default access spec for this module.
Attr defaultAccess_{Attr::PUBLIC};
const SourceName *prevAccessStmt_{nullptr};
// The scope of the module during a UseStmt
const Scope *useModuleScope_{nullptr};
+ // Directories to search for .mod files
+ std::vector<std::string> searchDirectories_;
+
void SetAccess(const parser::Name &, Attr);
void ApplyDefaultAccess();
-
+ const Scope *FindModule(const SourceName &);
void AddUse(const parser::Rename::Names &);
void AddUse(const parser::Name &);
// Record a use from useModuleScope_ of useName as localName. location is
// Set useModuleScope_ to the Scope of the module being used.
bool ModuleVisitor::Pre(const parser::UseStmt &x) {
- // x.nature = UseStmt::ModuleNature::Intrinsic or Non_Intrinsic
- const auto it{Scope::globalScope.find(x.moduleName.source)};
- if (it == Scope::globalScope.end()) {
- Say(x.moduleName, "Module '%s' not found"_err_en_US);
- return false;
- }
- const auto *details{it->second->detailsIf<ModuleDetails>()};
- if (!details) {
- Say(x.moduleName, "'%s' is not a module"_err_en_US);
- return false;
- }
- useModuleScope_ = details->scope();
- CHECK(useModuleScope_);
- return true;
+ useModuleScope_ = FindModule(x.moduleName.source);
+ return useModuleScope_ != nullptr;
}
void ModuleVisitor::Post(const parser::UseStmt &x) {
if (const auto *list{std::get_if<std::list<parser::Rename>>(&x.u)}) {
useModuleScope_ = nullptr;
}
+// Find the module with this name and return its scope.
+// May have to read a .mod file to find it.
+// Return nullptr on error, after reporting it.
+const Scope *ModuleVisitor::FindModule(const SourceName &name) {
+ auto it{Scope::globalScope.find(name)};
+ if (it == Scope::globalScope.end()) {
+ ModFileReader reader{searchDirectories_};
+ if (!reader.Read(name)) {
+ for (auto &error : reader.errors()) {
+ Say(std::move(error));
+ }
+ return nullptr;
+ }
+ it = Scope::globalScope.find(name);
+ CHECK(it != Scope::globalScope.end()); // else would have reported error
+ }
+ const auto *details{it->second->detailsIf<ModuleDetails>()};
+ if (!details) {
+ Say(name, "'%s' is not a module"_err_en_US);
+ return nullptr;
+ }
+ return details->scope();
+}
+
void ModuleVisitor::AddUse(const parser::Rename::Names &names) {
const SourceName &useName{std::get<0>(names.t).source};
const SourceName &localName{std::get<1>(names.t).source};
void InterfaceVisitor::AddToGeneric(
const parser::Name &name, bool expectModuleProc) {
genericSymbol_->get<GenericDetails>().add_specificProcName(
- name.source, expectModuleProc);
+ name.source, expectModuleProc);
}
void InterfaceVisitor::AddToGeneric(const Symbol &symbol) {
genericSymbol_->get<GenericDetails>().add_specificProc(&symbol);
CHECK(!GetDeclTypeSpec());
}
-void ResolveNames(
- parser::Program &program, const parser::CookedSource &cookedSource) {
+void ResolveNames(parser::Program &program,
+ const parser::CookedSource &cookedSource,
+ const std::vector<std::string> &searchDirectories) {
ResolveNamesVisitor visitor;
+ for (auto &dir : searchDirectories) {
+ visitor.add_searchDirectory(dir);
+ }
parser::Walk(const_cast<const parser::Program &>(program), visitor);
if (!visitor.messages().empty()) {
visitor.messages().Emit(std::cerr, cookedSource);
#define FORTRAN_SEMANTICS_RESOLVE_NAMES_H_
#include <iosfwd>
+#include <vector>
namespace Fortran::parser {
struct Program;
namespace Fortran::semantics {
-void ResolveNames(parser::Program &, const parser::CookedSource &);
+void ResolveNames(parser::Program &, const parser::CookedSource &,
+ const std::vector<std::string> &);
void DumpSymbols(std::ostream &);
} // namespace Fortran::semantics
}
os << scope.children_.size() << " children\n";
for (const auto &pair : scope.symbols_) {
- const auto &symbol{pair.second};
- os << " " << symbol << '\n';
+ const auto *symbol{pair.second};
+ os << " " << *symbol << '\n';
}
return os;
}
DerivedTypeSpec &MakeDerivedTypeSpec(const SourceName &);
+ std::unique_ptr<parser::CookedSource> cooked_;
+ void set_cookedSource(std::unique_ptr<parser::CookedSource> cooked) {
+ cooked_ = std::move(cooked);
+ }
+
private:
Scope &parent_;
const Kind kind_;
class Symbol {
public:
- ENUM_CLASS(Flag, Function, Subroutine, Implicit);
+ ENUM_CLASS(Flag, Function, Subroutine, Implicit, ModFile);
using Flags = common::EnumSet<Flag, Flag_enumSize>;
const Scope &owner() const { return *owner_; }
end
use m1
-!ERROR: Module 'm2' not found
+!ERROR: Cannot find module file for 'm2'
use m2
!ERROR: 'sub' is not a module
use sub
PATH=/usr/bin:/bin
srcdir=$(dirname $0)
-CMD="${F18:-../../tools/f18/f18} -fdebug-resolve-names -fparse-only"
+CMD="${F18:-../../../tools/f18/f18} -fdebug-resolve-names -fparse-only"
if [[ $# != 1 ]]; then
echo "Usage: $0 <fortran-source>"
diffs=$temp/diffs
cmd="$CMD $src"
-$cmd > $log 2>&1
+( cd $temp; $cmd ) > $log 2>&1
[[ $? -ge 128 ]] && exit 1
# $actual has errors from the compiler; $expect has them from !ERROR comments in source
}
if (driver.debugResolveNames || driver.dumpSymbols ||
driver.dumpUnparseWithSymbols) {
- Fortran::semantics::ResolveNames(parseTree, parsing.cooked());
+ std::vector<std::string> directories{options.searchDirectories};
+ directories.insert(directories.begin(), "."s);
+ Fortran::semantics::ResolveNames(parseTree, parsing.cooked(), directories);
Fortran::semantics::WriteModFiles();
if (driver.dumpSymbols) {
Fortran::semantics::DumpSymbols(std::cout);
if (options.isStrictlyStandard) {
options.features.WarnOnAllNonstandard();
}
- if (!options.features.IsEnabled(Fortran::parser::LanguageFeature::BackslashEscapes)) {
+ if (!options.features.IsEnabled(
+ Fortran::parser::LanguageFeature::BackslashEscapes)) {
driver.pgf90Args.push_back("-Mbackslash");
}