From 143e9e2fca8d650b499a1becaea0237c6d95184b Mon Sep 17 00:00:00 2001 From: Peter Klausler Date: Sat, 25 May 2024 11:08:49 -0700 Subject: [PATCH] [flang][preprocessing] Handle #include after & line continuation 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. --- flang/lib/Parser/preprocessor.cpp | 19 ++++---- flang/lib/Parser/prescan.cpp | 54 ++++++++++++++--------- flang/lib/Parser/prescan.h | 7 ++- flang/test/Preprocessing/args.h | 1 + flang/test/Preprocessing/include-args.F90 | 6 +++ 5 files changed, 56 insertions(+), 31 deletions(-) create mode 100644 flang/test/Preprocessing/args.h create mode 100644 flang/test/Preprocessing/include-args.F90 diff --git a/flang/lib/Parser/preprocessor.cpp b/flang/lib/Parser/preprocessor.cpp index ce95dc4b7aaec..8896980bf4bbf 100644 --- a/flang/lib/Parser/preprocessor.cpp +++ b/flang/lib/Parser/preprocessor.cpp @@ -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), diff --git a/flang/lib/Parser/prescan.cpp b/flang/lib/Parser/prescan.cpp index c08a28cb43449..f9b9c3d2c6e56 100644 --- a/flang/lib/Parser/prescan.cpp +++ b/flang/lib/Parser/prescan.cpp @@ -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(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); } } diff --git a/flang/lib/Parser/prescan.h b/flang/lib/Parser/prescan.h index 4eb3713bd3e37..491b1fe0a7517 100644 --- a/flang/lib/Parser/prescan.h +++ b/flang/lib/Parser/prescan.h @@ -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 @@ class Prescanner { common::LanguageFeature::ClassicCComments))); } + void CheckAndEmitLine(TokenSequence &, Provenance newlineProvenance); void LabelField(TokenSequence &); void EnforceStupidEndStatementRules(const TokenSequence &); void SkipToEndOfLine(); @@ -198,6 +201,7 @@ class Prescanner { Preprocessor &preprocessor_; AllSources &allSources_; common::LanguageFeatureControl features_; + bool isNestedInIncludeDirective_{false}; bool backslashFreeFormContinuation_{false}; bool inFixedForm_{false}; int fixedFormColumnLimit_{72}; @@ -206,6 +210,7 @@ class Prescanner { int prescannerNesting_{0}; int continuationLines_{0}; bool isPossibleMacroCall_{false}; + bool afterIncludeDirective_{false}; Provenance startProvenance_; const char *start_{nullptr}; // beginning of current source file content diff --git a/flang/test/Preprocessing/args.h b/flang/test/Preprocessing/args.h new file mode 100644 index 0000000000000..071ebae9cb3c2 --- /dev/null +++ b/flang/test/Preprocessing/args.h @@ -0,0 +1 @@ +3.14159 & diff --git a/flang/test/Preprocessing/include-args.F90 b/flang/test/Preprocessing/include-args.F90 new file mode 100644 index 0000000000000..011e4dba13e73 --- /dev/null +++ b/flang/test/Preprocessing/include-args.F90 @@ -0,0 +1,6 @@ +! RUN: %flang -E %s 2>&1 | FileCheck %s +! CHECK: call foo(3.14159) +call foo (& +#include "args.h" +) +end