[flang][preprocessing] Handle #include after & line continuation (#93382)

Some applications like to use a CPP-style #include directive to pull in
a common list of arguments, dummy arguments, or COMMON block variables
after a free-form & line continuation marker. This works naturally with
compilers that run an actual cpp pass over the input before doing
anything specific to Fortran, but it's a case that I missed with this
integrated preprocessor.
This commit is contained in:
Peter Klausler 2024-06-03 12:18:30 -07:00 committed by GitHub
parent 68f4e46c43
commit 0525c201b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 56 additions and 31 deletions

View File

@ -749,17 +749,18 @@ void Preprocessor::Directive(const TokenSequence &dir, Prescanner &prescanner) {
}
std::string buf;
llvm::raw_string_ostream error{buf};
const SourceFile *included{
allSources_.Open(include, error, std::move(prependPath))};
if (!included) {
if (const SourceFile *
included{allSources_.Open(include, error, std::move(prependPath))}) {
if (included->bytes() > 0) {
ProvenanceRange fileRange{
allSources_.AddIncludedFile(*included, dir.GetProvenanceRange())};
Prescanner{prescanner, /*isNestedInIncludeDirective=*/true}
.set_encoding(included->encoding())
.Prescan(fileRange);
}
} else {
prescanner.Say(dir.GetTokenProvenanceRange(j), "#include: %s"_err_en_US,
error.str());
} else if (included->bytes() > 0) {
ProvenanceRange fileRange{
allSources_.AddIncludedFile(*included, dir.GetProvenanceRange())};
Prescanner{prescanner}
.set_encoding(included->encoding())
.Prescan(fileRange);
}
} else {
prescanner.Say(dir.GetTokenProvenanceRange(dirOffset),

View File

@ -32,10 +32,11 @@ Prescanner::Prescanner(Messages &messages, CookedSource &cooked,
backslashFreeFormContinuation_{preprocessor.AnyDefinitions()},
encoding_{allSources_.encoding()} {}
Prescanner::Prescanner(const Prescanner &that)
Prescanner::Prescanner(const Prescanner &that, bool isNestedInIncludeDirective)
: messages_{that.messages_}, cooked_{that.cooked_},
preprocessor_{that.preprocessor_}, allSources_{that.allSources_},
features_{that.features_},
isNestedInIncludeDirective_{isNestedInIncludeDirective},
backslashFreeFormContinuation_{that.backslashFreeFormContinuation_},
inFixedForm_{that.inFixedForm_},
fixedFormColumnLimit_{that.fixedFormColumnLimit_},
@ -104,11 +105,14 @@ void Prescanner::Statement() {
NextLine();
return;
case LineClassification::Kind::ConditionalCompilationDirective:
case LineClassification::Kind::IncludeDirective:
case LineClassification::Kind::DefinitionDirective:
case LineClassification::Kind::PreprocessorDirective:
preprocessor_.Directive(TokenizePreprocessorDirective(), *this);
return;
case LineClassification::Kind::IncludeDirective:
preprocessor_.Directive(TokenizePreprocessorDirective(), *this);
afterIncludeDirective_ = true;
return;
case LineClassification::Kind::CompilerDirective: {
directiveSentinel_ = line.sentinel;
CHECK(InCompilerDirective());
@ -213,10 +217,7 @@ void Prescanner::Statement() {
Say(preprocessed->GetProvenanceRange(),
"Preprocessed line resembles a preprocessor directive"_warn_en_US);
}
preprocessed->ToLowerCase()
.CheckBadFortranCharacters(messages_, *this)
.CheckBadParentheses(messages_)
.Emit(cooked_);
CheckAndEmitLine(preprocessed->ToLowerCase(), newlineProvenance);
break;
case LineClassification::Kind::CompilerDirective:
if (preprocessed->HasRedundantBlanks()) {
@ -228,10 +229,9 @@ void Prescanner::Statement() {
NormalizeCompilerDirectiveCommentMarker(*preprocessed);
preprocessed->ToLowerCase();
SourceFormChange(preprocessed->ToString());
preprocessed->ClipComment(*this, true /* skip first ! */)
.CheckBadFortranCharacters(messages_, *this)
.CheckBadParentheses(messages_)
.Emit(cooked_);
CheckAndEmitLine(preprocessed->ToLowerCase().ClipComment(
*this, true /* skip first ! */),
newlineProvenance);
break;
case LineClassification::Kind::Source:
if (inFixedForm_) {
@ -246,14 +246,11 @@ void Prescanner::Statement() {
preprocessed->RemoveRedundantBlanks();
}
}
preprocessed->ToLowerCase()
.ClipComment(*this)
.CheckBadFortranCharacters(messages_, *this)
.CheckBadParentheses(messages_)
.Emit(cooked_);
CheckAndEmitLine(
preprocessed->ToLowerCase().ClipComment(*this), newlineProvenance);
break;
}
} else {
} else { // no macro replacement
if (line.kind == LineClassification::Kind::CompilerDirective) {
while (CompilerDirectiveContinuation(tokens, line.sentinel)) {
newlineProvenance = GetCurrentProvenance();
@ -266,16 +263,29 @@ void Prescanner::Statement() {
EnforceStupidEndStatementRules(tokens);
}
}
tokens.CheckBadFortranCharacters(messages_, *this)
.CheckBadParentheses(messages_)
.Emit(cooked_);
CheckAndEmitLine(tokens, newlineProvenance);
}
directiveSentinel_ = nullptr;
}
void Prescanner::CheckAndEmitLine(
TokenSequence &tokens, Provenance newlineProvenance) {
tokens.CheckBadFortranCharacters(messages_, *this);
// Parenthesis nesting check does not apply while any #include is
// active, nor on the lines before and after a top-level #include.
// Applications play shenanigans with line continuation before and
// after #include'd subprogram argument lists.
if (!isNestedInIncludeDirective_ && !omitNewline_ &&
!afterIncludeDirective_) {
tokens.CheckBadParentheses(messages_);
}
tokens.Emit(cooked_);
if (omitNewline_) {
omitNewline_ = false;
} else {
cooked_.Put('\n', newlineProvenance);
afterIncludeDirective_ = false;
}
directiveSentinel_ = nullptr;
}
TokenSequence Prescanner::TokenizePreprocessorDirective() {
@ -985,7 +995,9 @@ void Prescanner::FortranInclude(const char *firstQuote) {
provenance, static_cast<std::size_t>(p - nextLine_)};
ProvenanceRange fileRange{
allSources_.AddIncludedFile(*included, includeLineRange)};
Prescanner{*this}.set_encoding(included->encoding()).Prescan(fileRange);
Prescanner{*this, /*isNestedInIncludeDirective=*/false}
.set_encoding(included->encoding())
.Prescan(fileRange);
}
}

View File

@ -35,7 +35,9 @@ class Prescanner {
public:
Prescanner(Messages &, CookedSource &, Preprocessor &,
common::LanguageFeatureControl);
Prescanner(const Prescanner &);
Prescanner(const Prescanner &, bool isNestedInIncludeDirective);
Prescanner(const Prescanner &) = delete;
Prescanner(Prescanner &&) = delete;
const AllSources &allSources() const { return allSources_; }
AllSources &allSources() { return allSources_; }
@ -155,6 +157,7 @@ private:
common::LanguageFeature::ClassicCComments)));
}
void CheckAndEmitLine(TokenSequence &, Provenance newlineProvenance);
void LabelField(TokenSequence &);
void EnforceStupidEndStatementRules(const TokenSequence &);
void SkipToEndOfLine();
@ -198,6 +201,7 @@ private:
Preprocessor &preprocessor_;
AllSources &allSources_;
common::LanguageFeatureControl features_;
bool isNestedInIncludeDirective_{false};
bool backslashFreeFormContinuation_{false};
bool inFixedForm_{false};
int fixedFormColumnLimit_{72};
@ -206,6 +210,7 @@ private:
int prescannerNesting_{0};
int continuationLines_{0};
bool isPossibleMacroCall_{false};
bool afterIncludeDirective_{false};
Provenance startProvenance_;
const char *start_{nullptr}; // beginning of current source file content

View File

@ -0,0 +1 @@
3.14159 &

View File

@ -0,0 +1,6 @@
! RUN: %flang -E %s 2>&1 | FileCheck %s
! CHECK: call foo(3.14159)
call foo (&
#include "args.h"
)
end