llvm-project/flang/lib/Parser/parsing.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

248 lines
9.0 KiB
C++
Raw Normal View History

//===-- lib/Parser/parsing.cpp --------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "flang/Parser/parsing.h"
#include "preprocessor.h"
#include "prescan.h"
#include "type-parsers.h"
#include "flang/Parser/message.h"
#include "flang/Parser/provenance.h"
#include "flang/Parser/source.h"
#include "llvm/Support/raw_ostream.h"
namespace Fortran::parser {
Parsing::Parsing(AllCookedSources &allCooked) : allCooked_{allCooked} {}
Parsing::~Parsing() {}
[flang] Improve module file reading and writing Fix problems with writing a mod file while another compilation is reading or writing. Write to a temp and then rename it: - compute the new contents of the .mod file - if it already exists, check if it is already correct - if not, write new contents to a temp file - rename the temp to the final destination `mkstemps()` seems to be the best way to create the temp file. It returns a file descriptor, so change the rest of the mod file writing to use POSIX open/read/write/close. This seems to set errno more reliably too. There is some extra work around creating the temp to make it have the same directory and suffix as the final file (so that if one gets left behind by a crash, "rm *.mod" still cleans it up). `mkstemps()` creates file with 0600 permissions so try to change it to what it would have been if we just wrote the file. Change module file reading to only read the file once; we used to read it to verify the checksum and then again to parse it. Instead, change `Parsing` so that we can get the file contents after `Prescan()` and use that to verify the checksum. Also, it has a mechanism for searching directories for files, so make use of that instead of duplicating that functionality in `ModFileReader`. This requires some changes to how errors are returned so they can be reported in the right place. Original-commit: flang-compiler/f18@d0d54971a5547820f8ed0271eb9818557bc8bc82 Reviewed-on: https://github.com/flang-compiler/f18/pull/758 Tree-same-pre-rewrite: false
2019-09-24 08:10:58 +08:00
const SourceFile *Parsing::Prescan(const std::string &path, Options options) {
options_ = options;
AllSources &allSources{allCooked_.allSources()};
allSources.ClearSearchPath();
[flang] Improve module file reading and writing Fix problems with writing a mod file while another compilation is reading or writing. Write to a temp and then rename it: - compute the new contents of the .mod file - if it already exists, check if it is already correct - if not, write new contents to a temp file - rename the temp to the final destination `mkstemps()` seems to be the best way to create the temp file. It returns a file descriptor, so change the rest of the mod file writing to use POSIX open/read/write/close. This seems to set errno more reliably too. There is some extra work around creating the temp to make it have the same directory and suffix as the final file (so that if one gets left behind by a crash, "rm *.mod" still cleans it up). `mkstemps()` creates file with 0600 permissions so try to change it to what it would have been if we just wrote the file. Change module file reading to only read the file once; we used to read it to verify the checksum and then again to parse it. Instead, change `Parsing` so that we can get the file contents after `Prescan()` and use that to verify the checksum. Also, it has a mechanism for searching directories for files, so make use of that instead of duplicating that functionality in `ModFileReader`. This requires some changes to how errors are returned so they can be reported in the right place. Original-commit: flang-compiler/f18@d0d54971a5547820f8ed0271eb9818557bc8bc82 Reviewed-on: https://github.com/flang-compiler/f18/pull/758 Tree-same-pre-rewrite: false
2019-09-24 08:10:58 +08:00
if (options.isModuleFile) {
for (const auto &path : options.searchDirectories) {
allSources.AppendSearchPathDirectory(path);
[flang] Improve module file reading and writing Fix problems with writing a mod file while another compilation is reading or writing. Write to a temp and then rename it: - compute the new contents of the .mod file - if it already exists, check if it is already correct - if not, write new contents to a temp file - rename the temp to the final destination `mkstemps()` seems to be the best way to create the temp file. It returns a file descriptor, so change the rest of the mod file writing to use POSIX open/read/write/close. This seems to set errno more reliably too. There is some extra work around creating the temp to make it have the same directory and suffix as the final file (so that if one gets left behind by a crash, "rm *.mod" still cleans it up). `mkstemps()` creates file with 0600 permissions so try to change it to what it would have been if we just wrote the file. Change module file reading to only read the file once; we used to read it to verify the checksum and then again to parse it. Instead, change `Parsing` so that we can get the file contents after `Prescan()` and use that to verify the checksum. Also, it has a mechanism for searching directories for files, so make use of that instead of duplicating that functionality in `ModFileReader`. This requires some changes to how errors are returned so they can be reported in the right place. Original-commit: flang-compiler/f18@d0d54971a5547820f8ed0271eb9818557bc8bc82 Reviewed-on: https://github.com/flang-compiler/f18/pull/758 Tree-same-pre-rewrite: false
2019-09-24 08:10:58 +08:00
}
}
std::string buf;
llvm::raw_string_ostream fileError{buf};
const SourceFile *sourceFile;
if (path == "-") {
sourceFile = allSources.ReadStandardInput(fileError);
} else if (options.isModuleFile) {
// Don't mess with intrinsic module search path
sourceFile = allSources.Open(path, fileError);
} else {
sourceFile =
allSources.Open(path, fileError, "."s /*prepend to search path*/);
}
[flang] Improve module file reading and writing Fix problems with writing a mod file while another compilation is reading or writing. Write to a temp and then rename it: - compute the new contents of the .mod file - if it already exists, check if it is already correct - if not, write new contents to a temp file - rename the temp to the final destination `mkstemps()` seems to be the best way to create the temp file. It returns a file descriptor, so change the rest of the mod file writing to use POSIX open/read/write/close. This seems to set errno more reliably too. There is some extra work around creating the temp to make it have the same directory and suffix as the final file (so that if one gets left behind by a crash, "rm *.mod" still cleans it up). `mkstemps()` creates file with 0600 permissions so try to change it to what it would have been if we just wrote the file. Change module file reading to only read the file once; we used to read it to verify the checksum and then again to parse it. Instead, change `Parsing` so that we can get the file contents after `Prescan()` and use that to verify the checksum. Also, it has a mechanism for searching directories for files, so make use of that instead of duplicating that functionality in `ModFileReader`. This requires some changes to how errors are returned so they can be reported in the right place. Original-commit: flang-compiler/f18@d0d54971a5547820f8ed0271eb9818557bc8bc82 Reviewed-on: https://github.com/flang-compiler/f18/pull/758 Tree-same-pre-rewrite: false
2019-09-24 08:10:58 +08:00
if (!fileError.str().empty()) {
ProvenanceRange range{allSources.AddCompilerInsertion(path)};
messages_.Say(range, "%s"_err_en_US, fileError.str());
[flang] Improve module file reading and writing Fix problems with writing a mod file while another compilation is reading or writing. Write to a temp and then rename it: - compute the new contents of the .mod file - if it already exists, check if it is already correct - if not, write new contents to a temp file - rename the temp to the final destination `mkstemps()` seems to be the best way to create the temp file. It returns a file descriptor, so change the rest of the mod file writing to use POSIX open/read/write/close. This seems to set errno more reliably too. There is some extra work around creating the temp to make it have the same directory and suffix as the final file (so that if one gets left behind by a crash, "rm *.mod" still cleans it up). `mkstemps()` creates file with 0600 permissions so try to change it to what it would have been if we just wrote the file. Change module file reading to only read the file once; we used to read it to verify the checksum and then again to parse it. Instead, change `Parsing` so that we can get the file contents after `Prescan()` and use that to verify the checksum. Also, it has a mechanism for searching directories for files, so make use of that instead of duplicating that functionality in `ModFileReader`. This requires some changes to how errors are returned so they can be reported in the right place. Original-commit: flang-compiler/f18@d0d54971a5547820f8ed0271eb9818557bc8bc82 Reviewed-on: https://github.com/flang-compiler/f18/pull/758 Tree-same-pre-rewrite: false
2019-09-24 08:10:58 +08:00
return sourceFile;
}
CHECK(sourceFile);
[flang] Improve module file reading and writing Fix problems with writing a mod file while another compilation is reading or writing. Write to a temp and then rename it: - compute the new contents of the .mod file - if it already exists, check if it is already correct - if not, write new contents to a temp file - rename the temp to the final destination `mkstemps()` seems to be the best way to create the temp file. It returns a file descriptor, so change the rest of the mod file writing to use POSIX open/read/write/close. This seems to set errno more reliably too. There is some extra work around creating the temp to make it have the same directory and suffix as the final file (so that if one gets left behind by a crash, "rm *.mod" still cleans it up). `mkstemps()` creates file with 0600 permissions so try to change it to what it would have been if we just wrote the file. Change module file reading to only read the file once; we used to read it to verify the checksum and then again to parse it. Instead, change `Parsing` so that we can get the file contents after `Prescan()` and use that to verify the checksum. Also, it has a mechanism for searching directories for files, so make use of that instead of duplicating that functionality in `ModFileReader`. This requires some changes to how errors are returned so they can be reported in the right place. Original-commit: flang-compiler/f18@d0d54971a5547820f8ed0271eb9818557bc8bc82 Reviewed-on: https://github.com/flang-compiler/f18/pull/758 Tree-same-pre-rewrite: false
2019-09-24 08:10:58 +08:00
if (!options.isModuleFile) {
// For .mod files we always want to look in the search directories.
// For normal source files we don't add them until after the primary
[flang] Improve module file reading and writing Fix problems with writing a mod file while another compilation is reading or writing. Write to a temp and then rename it: - compute the new contents of the .mod file - if it already exists, check if it is already correct - if not, write new contents to a temp file - rename the temp to the final destination `mkstemps()` seems to be the best way to create the temp file. It returns a file descriptor, so change the rest of the mod file writing to use POSIX open/read/write/close. This seems to set errno more reliably too. There is some extra work around creating the temp to make it have the same directory and suffix as the final file (so that if one gets left behind by a crash, "rm *.mod" still cleans it up). `mkstemps()` creates file with 0600 permissions so try to change it to what it would have been if we just wrote the file. Change module file reading to only read the file once; we used to read it to verify the checksum and then again to parse it. Instead, change `Parsing` so that we can get the file contents after `Prescan()` and use that to verify the checksum. Also, it has a mechanism for searching directories for files, so make use of that instead of duplicating that functionality in `ModFileReader`. This requires some changes to how errors are returned so they can be reported in the right place. Original-commit: flang-compiler/f18@d0d54971a5547820f8ed0271eb9818557bc8bc82 Reviewed-on: https://github.com/flang-compiler/f18/pull/758 Tree-same-pre-rewrite: false
2019-09-24 08:10:58 +08:00
// source file has been opened. If foo.f is missing from the current
// working directory, we don't want to accidentally read another foo.f
// from another directory that's on the search path.
for (const auto &path : options.searchDirectories) {
allSources.AppendSearchPathDirectory(path);
[flang] Improve module file reading and writing Fix problems with writing a mod file while another compilation is reading or writing. Write to a temp and then rename it: - compute the new contents of the .mod file - if it already exists, check if it is already correct - if not, write new contents to a temp file - rename the temp to the final destination `mkstemps()` seems to be the best way to create the temp file. It returns a file descriptor, so change the rest of the mod file writing to use POSIX open/read/write/close. This seems to set errno more reliably too. There is some extra work around creating the temp to make it have the same directory and suffix as the final file (so that if one gets left behind by a crash, "rm *.mod" still cleans it up). `mkstemps()` creates file with 0600 permissions so try to change it to what it would have been if we just wrote the file. Change module file reading to only read the file once; we used to read it to verify the checksum and then again to parse it. Instead, change `Parsing` so that we can get the file contents after `Prescan()` and use that to verify the checksum. Also, it has a mechanism for searching directories for files, so make use of that instead of duplicating that functionality in `ModFileReader`. This requires some changes to how errors are returned so they can be reported in the right place. Original-commit: flang-compiler/f18@d0d54971a5547820f8ed0271eb9818557bc8bc82 Reviewed-on: https://github.com/flang-compiler/f18/pull/758 Tree-same-pre-rewrite: false
2019-09-24 08:10:58 +08:00
}
}
Preprocessor preprocessor{allSources};
if (!options.predefinitions.empty()) {
preprocessor.DefineStandardMacros();
for (const auto &predef : options.predefinitions) {
if (predef.second) {
preprocessor.Define(predef.first, *predef.second);
} else {
preprocessor.Undefine(predef.first);
}
}
}
currentCooked_ = &allCooked_.NewCookedSource();
Prescanner prescanner{
messages_, *currentCooked_, preprocessor, options.features};
prescanner.set_fixedForm(options.isFixedForm)
.set_fixedFormColumnLimit(options.fixedFormColumns)
.AddCompilerDirectiveSentinel("dir$");
if (options.features.IsEnabled(LanguageFeature::OpenACC)) {
prescanner.AddCompilerDirectiveSentinel("$acc");
}
if (options.features.IsEnabled(LanguageFeature::OpenMP)) {
prescanner.AddCompilerDirectiveSentinel("$omp");
prescanner.AddCompilerDirectiveSentinel("$"); // OMP conditional line
}
ProvenanceRange range{allSources.AddIncludedFile(
*sourceFile, ProvenanceRange{}, options.isModuleFile)};
prescanner.Prescan(range);
if (currentCooked_->BufferedBytes() == 0 && !options.isModuleFile) {
// Input is empty. Append a newline so that any warning
// message about nonstandard usage will have provenance.
currentCooked_->Put('\n', range.start());
}
currentCooked_->Marshal(allCooked_);
if (options.needProvenanceRangeToCharBlockMappings) {
currentCooked_->CompileProvenanceRangeToOffsetMappings(allSources);
}
[flang] Improve module file reading and writing Fix problems with writing a mod file while another compilation is reading or writing. Write to a temp and then rename it: - compute the new contents of the .mod file - if it already exists, check if it is already correct - if not, write new contents to a temp file - rename the temp to the final destination `mkstemps()` seems to be the best way to create the temp file. It returns a file descriptor, so change the rest of the mod file writing to use POSIX open/read/write/close. This seems to set errno more reliably too. There is some extra work around creating the temp to make it have the same directory and suffix as the final file (so that if one gets left behind by a crash, "rm *.mod" still cleans it up). `mkstemps()` creates file with 0600 permissions so try to change it to what it would have been if we just wrote the file. Change module file reading to only read the file once; we used to read it to verify the checksum and then again to parse it. Instead, change `Parsing` so that we can get the file contents after `Prescan()` and use that to verify the checksum. Also, it has a mechanism for searching directories for files, so make use of that instead of duplicating that functionality in `ModFileReader`. This requires some changes to how errors are returned so they can be reported in the right place. Original-commit: flang-compiler/f18@d0d54971a5547820f8ed0271eb9818557bc8bc82 Reviewed-on: https://github.com/flang-compiler/f18/pull/758 Tree-same-pre-rewrite: false
2019-09-24 08:10:58 +08:00
return sourceFile;
}
void Parsing::EmitPreprocessedSource(
llvm::raw_ostream &out, bool lineDirectives) const {
const SourceFile *sourceFile{nullptr};
int sourceLine{0};
int column{1};
bool inDirective{false};
bool inContinuation{false};
bool lineWasBlankBefore{true};
const AllSources &allSources{allCooked().allSources()};
// All directives that flang support are known to have a length of 3 chars
constexpr int directiveNameLength{3};
// We need to know the current directive in order to provide correct
// continuation for the directive
std::string directive;
for (const char &atChar : cooked().AsCharBlock()) {
char ch{atChar};
if (ch == '\n') {
out << '\n'; // TODO: DOS CR-LF line ending if necessary
column = 1;
inDirective = false;
inContinuation = false;
lineWasBlankBefore = true;
++sourceLine;
directive.clear();
} else {
auto provenance{cooked().GetProvenanceRange(CharBlock{&atChar, 1})};
// Preserves original case of the character
const auto getOriginalChar{[&](char ch) {
if (IsLetter(ch) && provenance && provenance->size() == 1) {
if (const char *orig{allSources.GetSource(*provenance)}) {
const char upper{ToUpperCaseLetter(ch)};
if (*orig == upper) {
return upper;
}
}
}
return ch;
}};
if (ch == '!' && lineWasBlankBefore) {
// Other comment markers (C, *, D) in original fixed form source
// input card column 1 will have been deleted or normalized to !,
// which signifies a comment (directive) in both source forms.
inDirective = true;
}
if (inDirective && directive.size() < directiveNameLength &&
IsLetter(ch)) {
directive += getOriginalChar(ch);
}
std::optional<SourcePosition> position{provenance
? allSources.GetSourcePosition(provenance->start())
: std::nullopt};
if (lineDirectives && column == 1 && position) {
if (&position->file != sourceFile) {
out << "#line \"" << position->file.path() << "\" " << position->line
<< '\n';
} else if (position->line != sourceLine) {
if (sourceLine < position->line &&
sourceLine + 10 >= position->line) {
// Emit a few newlines to catch up when they'll likely
// require fewer bytes than a #line directive would have
// occupied.
while (sourceLine++ < position->line) {
out << '\n';
}
} else {
out << "#line " << position->line << '\n';
}
}
sourceFile = &position->file;
sourceLine = position->line;
}
if (column > 72) {
// Wrap long lines in a portable fashion that works in both
// of the Fortran source forms. The first free-form continuation
// marker ("&") lands in column 73, which begins the card commentary
// field of fixed form, and the second one is put in column 6,
// where it signifies fixed form line continuation.
// The standard Fortran fixed form column limit (72) is used
// for output, even if the input was parsed with a nonstandard
// column limit override option.
// OpenMP and OpenACC directives' continuations should have the
// corresponding sentinel at the next line.
const auto continuation{
inDirective ? "&\n!$" + directive + "&" : "&\n &"s};
out << continuation;
column = 7; // start of fixed form source field
++sourceLine;
inContinuation = true;
} else if (!inDirective && ch != ' ' && (ch < '0' || ch > '9')) {
// Put anything other than a label or directive into the
// Fortran fixed form source field (columns [7:72]).
for (; column < 7; ++column) {
out << ' ';
}
}
if (!inContinuation && position && position->column <= 72 && ch != ' ') {
// Preserve original indentation
for (; column < position->column; ++column) {
out << ' ';
}
}
out << getOriginalChar(ch);
lineWasBlankBefore = ch == ' ' && lineWasBlankBefore;
++column;
}
}
}
void Parsing::DumpCookedChars(llvm::raw_ostream &out) const {
UserState userState{allCooked_, common::LanguageFeatureControl{}};
ParseState parseState{cooked()};
parseState.set_inFixedForm(options_.isFixedForm).set_userState(&userState);
while (std::optional<const char *> p{parseState.GetNextChar()}) {
out << **p;
}
}
void Parsing::DumpProvenance(llvm::raw_ostream &out) const {
allCooked_.Dump(out);
}
void Parsing::DumpParsingLog(llvm::raw_ostream &out) const {
log_.Dump(out, allCooked_);
}
void Parsing::Parse(llvm::raw_ostream &out) {
UserState userState{allCooked_, options_.features};
userState.set_debugOutput(out)
.set_instrumentedParse(options_.instrumentedParse)
.set_log(&log_);
ParseState parseState{cooked()};
parseState.set_inFixedForm(options_.isFixedForm).set_userState(&userState);
parseTree_ = program.Parse(parseState);
CHECK(
!parseState.anyErrorRecovery() || parseState.messages().AnyFatalError());
consumedWholeFile_ = parseState.IsAtEnd();
messages_.Annex(std::move(parseState.messages()));
finalRestingPlace_ = parseState.GetLocation();
}
void Parsing::ClearLog() { log_.clear(); }
} // namespace Fortran::parser