Skip to content

Commit 1ecc000

Browse files
committed
[flang][preprocessor] Handle line continuations after macro expansion
There are cases in which free source form line continuation is not detectable before macro expansion. The '&' continuation marker can be the result of a macro expansion, and (for compiler directives) the continuation line may not be recognizable as such before its continuation sentinel (e.g., "!$omp &") is produced by macro expansion. Check for preprocessed compiler directive and source lines that end in '&' after macro expansion, and in both cases attempt to locate and preprocess their continuation lines. Also check for compiler directive continuation lines that might result from macro replacement after an initial compiler directive line that had no macro replacement. Last, also allow a continuation marker (&) to appear in a macro actual argument.
1 parent 134c915 commit 1ecc000

File tree

5 files changed

+168
-10
lines changed

5 files changed

+168
-10
lines changed

flang/docs/Preprocessing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,5 +228,5 @@ E . . E E . pp125.F90 #DEFINE works in free form
228228
. . E . E E pp127.F90 FLM call with closing ')' on next line (not a continuation)
229229
E . E . E E pp128.F90 FLM call with '(' on next line (not a continuation)
230230
. . N . . N pp129.F90 #define KWM !, then KWM works as comment line initiator
231-
E . E . . E pp130.F90 #define KWM &, use for continuation w/o pasting (ifort and nag seem to continue #define)
231+
. . E . . E pp130.F90 #define KWM &, use for continuation w/o pasting (ifort and nag seem to continue #define)
232232
```

flang/lib/Parser/prescan.cpp

Lines changed: 122 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,9 @@ void Prescanner::Statement() {
213213
if (preprocessed->HasRedundantBlanks()) {
214214
preprocessed->RemoveRedundantBlanks();
215215
}
216+
while (CompilerDirectiveContinuation(*preprocessed, ppl.sentinel)) {
217+
newlineProvenance = GetCurrentProvenance();
218+
}
216219
NormalizeCompilerDirectiveCommentMarker(*preprocessed);
217220
preprocessed->ToLowerCase();
218221
SourceFormChange(preprocessed->ToString());
@@ -227,6 +230,9 @@ void Prescanner::Statement() {
227230
preprocessed->RemoveBlanks(/*after column*/ 6);
228231
}
229232
} else {
233+
while (SourceLineContinuation(*preprocessed)) {
234+
newlineProvenance = GetCurrentProvenance();
235+
}
230236
if (preprocessed->HasRedundantBlanks()) {
231237
preprocessed->RemoveRedundantBlanks();
232238
}
@@ -239,12 +245,17 @@ void Prescanner::Statement() {
239245
break;
240246
}
241247
} else {
242-
tokens.ToLowerCase();
243248
if (line.kind == LineClassification::Kind::CompilerDirective) {
249+
while (CompilerDirectiveContinuation(tokens, line.sentinel)) {
250+
newlineProvenance = GetCurrentProvenance();
251+
}
252+
tokens.ToLowerCase();
244253
SourceFormChange(tokens.ToString());
245-
}
246-
if (inFixedForm_ && line.kind == LineClassification::Kind::Source) {
247-
EnforceStupidEndStatementRules(tokens);
254+
} else { // Kind::Source
255+
tokens.ToLowerCase();
256+
if (inFixedForm_) {
257+
EnforceStupidEndStatementRules(tokens);
258+
}
248259
}
249260
tokens.CheckBadFortranCharacters(messages_)
250261
.CheckBadParentheses(messages_)
@@ -1132,8 +1143,10 @@ bool Prescanner::FreeFormContinuation() {
11321143
if (*p != '\n') {
11331144
if (inCharLiteral_) {
11341145
return false;
1135-
} else if (*p != '!' &&
1136-
features_.ShouldWarn(LanguageFeature::CruftAfterAmpersand)) {
1146+
} else if (*p == '!') { // & ! comment - ok
1147+
} else if (ampersand && isPossibleMacroCall_ && (*p == ',' || *p == ')')) {
1148+
return false; // allow & at end of a macro argument
1149+
} else if (features_.ShouldWarn(LanguageFeature::CruftAfterAmpersand)) {
11371150
Say(GetProvenance(p), "missing ! before comment after &"_warn_en_US);
11381151
}
11391152
}
@@ -1318,4 +1331,107 @@ void Prescanner::SourceFormChange(std::string &&dir) {
13181331
inFixedForm_ = true;
13191332
}
13201333
}
1334+
1335+
// Acquire and append compiler directive continuation lines to
1336+
// the tokens that constitute a compiler directive, even when those
1337+
// directive continuation lines are the result of macro expansion.
1338+
// (Not used when neither the original compiler directive line nor
1339+
// the directive continuation line result from preprocessing; regular
1340+
// line continuation during tokenization handles that normal case.)
1341+
bool Prescanner::CompilerDirectiveContinuation(
1342+
TokenSequence &tokens, const char *origSentinel) {
1343+
if (inFixedForm_ || tokens.empty() ||
1344+
tokens.TokenAt(tokens.SizeInTokens() - 1) != "&") {
1345+
return false;
1346+
}
1347+
LineClassification followingLine{ClassifyLine(nextLine_)};
1348+
if (followingLine.kind == LineClassification::Kind::Comment) {
1349+
nextLine_ += followingLine.payloadOffset; // advance to '!' or newline
1350+
NextLine();
1351+
return true;
1352+
}
1353+
CHECK(origSentinel != nullptr);
1354+
directiveSentinel_ = origSentinel; // so IsDirective() is true
1355+
const char *nextContinuation{
1356+
followingLine.kind == LineClassification::Kind::CompilerDirective
1357+
? FreeFormContinuationLine(true)
1358+
: nullptr};
1359+
if (!nextContinuation &&
1360+
followingLine.kind != LineClassification::Kind::Source) {
1361+
return false;
1362+
}
1363+
auto origNextLine{nextLine_};
1364+
BeginSourceLine(nextLine_);
1365+
NextLine();
1366+
TokenSequence followingTokens;
1367+
if (nextContinuation) {
1368+
// What follows is !DIR$ & xxx; skip over the & so that it
1369+
// doesn't cause a spurious continuation.
1370+
at_ = nextContinuation;
1371+
} else {
1372+
// What follows looks like a source line before macro expansion,
1373+
// but might become a directive continuation afterwards.
1374+
SkipSpaces();
1375+
}
1376+
while (NextToken(followingTokens)) {
1377+
}
1378+
if (auto followingPrepro{
1379+
preprocessor_.MacroReplacement(followingTokens, *this)}) {
1380+
followingTokens = std::move(*followingPrepro);
1381+
}
1382+
followingTokens.RemoveRedundantBlanks();
1383+
std::size_t startAt{0};
1384+
std::size_t keep{followingTokens.SizeInTokens()};
1385+
bool ok{false};
1386+
if (nextContinuation) {
1387+
ok = true;
1388+
} else {
1389+
if (keep >= 3 && followingTokens.TokenAt(0) == "!" &&
1390+
followingTokens.TokenAt(2) == "&") {
1391+
CharBlock sentinel{followingTokens.TokenAt(1)};
1392+
if (!sentinel.empty() &&
1393+
std::memcmp(sentinel.begin(), origSentinel, sentinel.size()) == 0) {
1394+
startAt = 3;
1395+
keep -= 3;
1396+
ok = true;
1397+
}
1398+
}
1399+
}
1400+
if (ok) {
1401+
tokens.pop_back(); // delete original '&'
1402+
tokens.Put(followingTokens, startAt, keep);
1403+
} else {
1404+
nextLine_ = origNextLine;
1405+
}
1406+
return ok;
1407+
}
1408+
1409+
// Similar, but for source line continuation after macro replacement.
1410+
bool Prescanner::SourceLineContinuation(TokenSequence &tokens) {
1411+
if (!inFixedForm_ && !tokens.empty() &&
1412+
tokens.TokenAt(tokens.SizeInTokens() - 1) == "&") {
1413+
LineClassification followingLine{ClassifyLine(nextLine_)};
1414+
if (followingLine.kind == LineClassification::Kind::Comment) {
1415+
nextLine_ += followingLine.payloadOffset; // advance to '!' or newline
1416+
NextLine();
1417+
return true;
1418+
} else if (const char *nextContinuation{FreeFormContinuationLine(true)}) {
1419+
BeginSourceLine(nextLine_);
1420+
NextLine();
1421+
TokenSequence followingTokens;
1422+
at_ = nextContinuation;
1423+
while (NextToken(followingTokens)) {
1424+
}
1425+
if (auto followingPrepro{
1426+
preprocessor_.MacroReplacement(followingTokens, *this)}) {
1427+
followingTokens = std::move(*followingPrepro);
1428+
}
1429+
followingTokens.RemoveRedundantBlanks();
1430+
tokens.pop_back(); // delete original '&'
1431+
tokens.Put(followingTokens);
1432+
return true;
1433+
}
1434+
}
1435+
return false;
1436+
}
13211437
} // namespace Fortran::parser

flang/lib/Parser/prescan.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ class Prescanner {
186186
const char *) const;
187187
LineClassification ClassifyLine(const char *) const;
188188
void SourceFormChange(std::string &&);
189+
bool CompilerDirectiveContinuation(TokenSequence &, const char *sentinel);
190+
bool SourceLineContinuation(TokenSequence &);
189191

190192
Messages &messages_;
191193
CookedSource &cooked_;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
! RUN: %flang -E %s 2>&1 | FileCheck %s
2+
3+
#define DIR_START !dir$
4+
#define DIR_CONT !dir$&
5+
#define FIRST(x) DIR_START x
6+
#define NEXT(x) DIR_CONT x
7+
#define AMPER &
8+
9+
subroutine s(x1, x2, x3, x4, x5, x6, x7)
10+
11+
!dir$ ignore_tkr x1
12+
13+
!dir$ ignore_tkr &
14+
!dir$& x2
15+
16+
DIR_START ignore_tkr x3
17+
18+
!dir$ ignore_tkr AMPER
19+
DIR_CONT x4
20+
21+
FIRST(ignore_tkr &)
22+
!dir$& x5
23+
24+
FIRST(ignore_tkr &)
25+
NEXT(x6)
26+
27+
FIRST(ignore_tkr &)
28+
NEXT(x7 &)
29+
NEXT(x8)
30+
31+
end
32+
33+
!CHECK: subroutine s(x1, x2, x3, x4, x5, x6, x7)
34+
!CHECK: !dir$ ignore_tkr x1
35+
!CHECK: !dir$ ignore_tkr x2
36+
!CHECK: !dir$ ignore_tkr x3
37+
!CHECK: !dir$ ignore_tkr x4
38+
!CHECK: !dir$ ignore_tkr x5
39+
!CHECK: !dir$ ignore_tkr x6
40+
!CHECK: !dir$ ignore_tkr x7 x8
41+
!CHECK: end

flang/test/Preprocessing/pp130.F90

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
! RUN: not %flang -E %s 2>&1 | FileCheck %s
2-
! CHECK: error: bad character ('&') in Fortran token
1+
! RUN: %flang -E %s 2>&1 | FileCheck %s
2+
! CHECK: j = j + 111
33
! #define KWM &, use for continuation w/o pasting (ifort and nag seem to continue #define)
44
#define KWM &
5-
65
integer :: j
76
j = 666
87
j = j + KWM

0 commit comments

Comments
 (0)