[flang-commits] [flang] [flang][preprocessor] Further macro replacement of continued identifiers (PR #134302)

Peter Klausler via flang-commits flang-commits at lists.llvm.org
Thu Apr 3 13:17:23 PDT 2025


https://github.com/klausler created https://github.com/llvm/llvm-project/pull/134302

The preprocessor can perform macro replacement within identifiers when they are split up with Fortran line continuation, but is failing to do macro replacement on a continued identifier when none of its parts are replaced.

>From acc910c278371898af285bbfddf4a34f138388e3 Mon Sep 17 00:00:00 2001
From: Peter Klausler <pklausler at nvidia.com>
Date: Thu, 3 Apr 2025 13:13:24 -0700
Subject: [PATCH] [flang][preprocessor] Further macro replacement of continued
 identifiers

The preprocessor can perform macro replacement within identifiers when
they are split up with Fortran line continuation, but is failing to do
macro replacement on a continued identifier when none of its parts
are replaced.
---
 flang/lib/Parser/prescan.cpp       | 57 ++++++++++++++++++------------
 flang/test/Preprocessing/pp047.F   | 25 +++++++++++++
 flang/test/Preprocessing/pp135.F90 | 25 +++++++++++++
 3 files changed, 85 insertions(+), 22 deletions(-)
 create mode 100644 flang/test/Preprocessing/pp047.F
 create mode 100644 flang/test/Preprocessing/pp135.F90

diff --git a/flang/lib/Parser/prescan.cpp b/flang/lib/Parser/prescan.cpp
index 0df1e3e291923..5df82e21a45b8 100644
--- a/flang/lib/Parser/prescan.cpp
+++ b/flang/lib/Parser/prescan.cpp
@@ -749,35 +749,48 @@ bool Prescanner::NextToken(TokenSequence &tokens) {
     }
     preventHollerith_ = false;
   } else if (IsLegalInIdentifier(*at_)) {
-    int parts{1};
-    const char *afterLast{nullptr};
+    std::size_t parts{1};
+    bool anyDefined{false};
+    bool hadContinuation{false};
+    // Subtlety: When an identifier is split across continuation lines,
+    // its parts are kept as distinct pp-tokens if that macro replacement
+    // should operate on them independently.  This trick accommodates the
+    // historic practice of using line continuation for token pasting after
+    // replacement.
+    // In free form, the macro to be replaced must have been preceded
+    // by '&' and followed by either '&' or, if last, the end of a line.
+    //   call &                call foo&        call foo&
+    //     &MACRO&      OR       &MACRO&   OR     &MACRO
+    //     &foo(...)             &(...)
     do {
       EmitChar(tokens, *at_);
       ++at_, ++column_;
-      afterLast = at_;
-      if (SkipToNextSignificantCharacter() && IsLegalIdentifierStart(*at_)) {
+      hadContinuation = SkipToNextSignificantCharacter();
+      if (hadContinuation && IsLegalIdentifierStart(*at_)) {
+        // Continued identifier
         tokens.CloseToken();
         ++parts;
+        if (!anyDefined &&
+            (parts > 2 || inFixedForm_ ||
+                (start > start_ && start[-1] == '&')) &&
+            preprocessor_.IsNameDefined(
+                tokens.TokenAt(tokens.SizeInTokens() - 1))) {
+          anyDefined = true;
+        }
       }
     } while (IsLegalInIdentifier(*at_));
-    if (parts >= 3) {
-      // Subtlety: When an identifier is split across three or more continuation
-      // lines (or two continuation lines, immediately preceded or followed
-      // by '&' free form continuation line markers, its parts are kept as
-      // distinct pp-tokens so that macro replacement operates on them
-      // independently.  This trick accommodates the historic practice of
-      // using line continuation for token pasting after replacement.
-    } else if (parts == 2) {
-      if (afterLast && afterLast < limit_) {
-        afterLast = SkipWhiteSpace(afterLast);
-      }
-      if ((start > start_ && start[-1] == '&') ||
-          (afterLast && afterLast < limit_ &&
-              (*afterLast == '&' || *afterLast == '\n'))) {
-        // call &                call foo&        call foo&
-        //   &MACRO&      OR       &MACRO&   OR     &MACRO
-        //   &foo(...)             &(...)
-      } else {
+    if (!anyDefined && parts > 1) {
+      tokens.CloseToken();
+      char after{*SkipWhiteSpace(at_)};
+      anyDefined = (hadContinuation || after == '\n' || after == '&') &&
+          preprocessor_.IsNameDefined(
+              tokens.TokenAt(tokens.SizeInTokens() - 1));
+      tokens.ReopenLastToken();
+    }
+    if (!anyDefined) {
+      // If no part was a defined macro, combine the parts into one so that
+      // the combination itself can be subject to macro replacement.
+      while (parts-- > 1) {
         tokens.ReopenLastToken();
       }
     }
diff --git a/flang/test/Preprocessing/pp047.F b/flang/test/Preprocessing/pp047.F
new file mode 100644
index 0000000000000..1d4f9f848e58a
--- /dev/null
+++ b/flang/test/Preprocessing/pp047.F
@@ -0,0 +1,25 @@
+! RUN: %flang -E %s 2>&1 | FileCheck %s
+#define FOO BAR
+#define FO BA
+#define OO AR
+! CHECK: print *,BAR, 1
+      print *,
+     +FOO
+     +, 1
+      print *,
+! CHECK: print *,FAR, 2
+     +F
+     +OO
+     +, 2
+! CHECK: print *,BAO, 3
+      print *,
+     +FO
+     +O
+     +, 3
+! CHECK: print *,BAR, 4
+      print *,
+     +F
+     +O
+     +O
+     +, 4
+      end
diff --git a/flang/test/Preprocessing/pp135.F90 b/flang/test/Preprocessing/pp135.F90
new file mode 100644
index 0000000000000..2905a8cec5d93
--- /dev/null
+++ b/flang/test/Preprocessing/pp135.F90
@@ -0,0 +1,25 @@
+! RUN: %flang -E %s 2>&1 | FileCheck %s
+#define FOO BAR
+#define FO BA
+#define OO AR
+! CHECK: print *, BAR, 1
+print *, &
+  &FOO&
+  &, 1
+! CHECK: print *, FAR, 2
+print *, &
+  &F&
+  &OO&
+  &, 2
+! CHECK: print *, BAO, 3
+print *, &
+  &FO&
+  &O&
+  &, 3
+! CHECK: print *, BAR, 4
+print *, &
+  &F&
+  &O&
+  &O&
+  &, 4
+end



More information about the flang-commits mailing list