[flang-commits] [flang] [llvm] [flang-rt] Extension: accept '!' as value separator in NAMELIST input (PR #200441)

Eugene Epshteyn via flang-commits flang-commits at lists.llvm.org
Wed Jun 3 14:48:54 PDT 2026


https://github.com/eugeneepshteyn updated https://github.com/llvm/llvm-project/pull/200441

>From f8deb92e949abb1c64ba7062f9eaf0ecf61e3f0b Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Fri, 29 May 2026 12:00:53 -0400
Subject: [PATCH 1/3] [flang-rt] Extension: accept '!' as value separator in
 NAMELIST input

Treat '!' as a self-delimiting value separator when reading NAMELIST
input, so that "name=value!comment" is accepted without an intervening
blank, comma, slash, or end of record. This matches gfortran, ifx, and
classic nvfortran behavior on real-world namelist input files.

F2023 13.11.3.6 p.1 requires a value separator before a '!' comment
introducer in namelist input, so this is a documented extension. The
change does not affect '!' characters inside character literal
constants, which continue to be taken literally.

The extension was documented in flang/docs/Extensions.md.

Assisted-by: AI
---
 flang-rt/lib/runtime/edit-input.cpp     |  2 +-
 flang-rt/lib/runtime/io-stmt.cpp        |  8 ++++
 flang-rt/unittests/Runtime/Namelist.cpp | 50 +++++++++++++++++++++++++
 flang/docs/Extensions.md                |  9 +++++
 4 files changed, 68 insertions(+), 1 deletion(-)

diff --git a/flang-rt/lib/runtime/edit-input.cpp b/flang-rt/lib/runtime/edit-input.cpp
index 23a684d0c0917..577e64d43421d 100644
--- a/flang-rt/lib/runtime/edit-input.cpp
+++ b/flang-rt/lib/runtime/edit-input.cpp
@@ -23,7 +23,7 @@ static inline RT_API_ATTRS bool IsCharValueSeparator(
     const DataEdit &edit, char32_t ch) {
   return ch == ' ' || ch == '\t' || ch == '/' ||
       ch == edit.modes.GetSeparatorChar() ||
-      (edit.IsNamelist() && (ch == '&' || ch == '$'));
+      (edit.IsNamelist() && (ch == '&' || ch == '$' || ch == '!'));
 }
 
 // Checks that a list-directed input value has been entirely consumed and
diff --git a/flang-rt/lib/runtime/io-stmt.cpp b/flang-rt/lib/runtime/io-stmt.cpp
index 9eb2dad8e457d..08931fc781428 100644
--- a/flang-rt/lib/runtime/io-stmt.cpp
+++ b/flang-rt/lib/runtime/io-stmt.cpp
@@ -685,6 +685,14 @@ common::optional<char32_t> IoStatementState::NextInField(
             return common::nullopt;
           }
           break;
+        case '!':
+          // Extension (gfortran, ifx, classic nvfortran): in NAMELIST input,
+          // '!' terminates a value even when not preceded by a separator,
+          // so that "name=value!comment" is accepted.
+          if (edit.IsNamelist()) {
+            return common::nullopt;
+          }
+          break;
         case ',':
           if (!(edit.modes.editingFlags & decimalComma)) {
             return common::nullopt;
diff --git a/flang-rt/unittests/Runtime/Namelist.cpp b/flang-rt/unittests/Runtime/Namelist.cpp
index f190bea14acfe..b81d9ec9d92bc 100644
--- a/flang-rt/unittests/Runtime/Namelist.cpp
+++ b/flang-rt/unittests/Runtime/Namelist.cpp
@@ -365,4 +365,54 @@ TEST(NamelistTests, NanInputAmbiguity) {
   EXPECT_EQ(got, expect);
 }
 
+// Tests that '!' terminates a NAMELIST value even without a preceding
+// value separator (extension matching gfortran, ifx, and classic nvfortran).
+TEST(NamelistTests, BangAsValueSeparator) {
+  OwningPtr<Descriptor> iDesc{
+      MakeArray<TypeCategory::Integer, static_cast<int>(sizeof(int))>(
+          std::vector<int>{}, std::vector<int>{0})};
+  OwningPtr<Descriptor> rDesc{
+      MakeArray<TypeCategory::Real, static_cast<int>(sizeof(float))>(
+          std::vector<int>{}, std::vector<float>{0.f})};
+  OwningPtr<Descriptor> lDesc{
+      MakeArray<TypeCategory::Logical, static_cast<int>(sizeof(std::uint8_t))>(
+          std::vector<int>{}, std::vector<std::uint8_t>{0})};
+  OwningPtr<Descriptor> zDesc{
+      MakeArray<TypeCategory::Complex, static_cast<int>(sizeof(float))>(
+          std::vector<int>{}, std::vector<std::complex<float>>{{0.f, 0.f}})};
+  OwningPtr<Descriptor> cDesc{MakeArray<TypeCategory::Character, 1>(
+      std::vector<int>{}, std::vector<std::string>{"        "}, 8)};
+  const NamelistGroup::Item items[]{{"i", *iDesc}, {"r", *rDesc},
+      {"l", *lDesc}, {"z", *zDesc}, {"c", *cDesc}};
+  const NamelistGroup group{"nml", 5, items};
+  // No space before any '!' — the '!' must still terminate each value
+  // and start a comment that runs to end of record.  Inside the character
+  // literal the '!' is preserved verbatim.
+  static char t1[]{"&nml i=42!comment       "
+                   " r=0.01!comment         "
+                   " l=.true.!comment       "
+                   " z=(1.,2.)!comment      "
+                   " c='a!b'!comment        "
+                   " /                      "};
+  StaticDescriptor<1, true> statDesc;
+  Descriptor &internalDesc{statDesc.descriptor()};
+  SubscriptValue shape{6};
+  internalDesc.Establish(1, 24, t1, 1, &shape, CFI_attribute_pointer);
+  auto inCookie{IONAME(BeginInternalArrayListInput)(
+      internalDesc, nullptr, 0, __FILE__, __LINE__)};
+  ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
+  ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk)
+      << "namelist input with '!' immediately after value";
+  EXPECT_EQ(*iDesc->ZeroBasedIndexedElement<int>(0), 42);
+  EXPECT_FLOAT_EQ(*rDesc->ZeroBasedIndexedElement<float>(0), 0.01f);
+  EXPECT_NE(*lDesc->ZeroBasedIndexedElement<std::uint8_t>(0), 0);
+  EXPECT_FLOAT_EQ(
+      zDesc->ZeroBasedIndexedElement<std::complex<float>>(0)->real(), 1.f);
+  EXPECT_FLOAT_EQ(
+      zDesc->ZeroBasedIndexedElement<std::complex<float>>(0)->imag(), 2.f);
+  std::string gotC{
+      cDesc->ZeroBasedIndexedElement<char>(0), cDesc->ElementBytes()};
+  EXPECT_EQ(gotC, "a!b     ");
+}
+
 // TODO: Internal NAMELIST error tests
diff --git a/flang/docs/Extensions.md b/flang/docs/Extensions.md
index 6583aa7d0444f..f574956f28a07 100644
--- a/flang/docs/Extensions.md
+++ b/flang/docs/Extensions.md
@@ -437,6 +437,15 @@ end
 * A `NAMELIST` input group may omit its trailing `/` character if
   it is followed by another `NAMELIST` input group.
 * A `NAMELIST` input group may begin with either `&` or `$`.
+* In `NAMELIST` input, a `!` character is accepted as terminating the
+  current value and introducing a comment even when it is not preceded
+  by a value separator.  For example, `name=0.01!comment` is accepted
+  as if it had been written `name=0.01 !comment`.  F2023 13.11.3.6 p.1
+  requires a value separator (blank, comma, slash, or end of record)
+  before a `!` comment introducer in namelist input, but gfortran,
+  ifx, and classic nvfortran all accept this form as a widely-used
+  extension.  Inside a character literal constant the `!` is taken
+  literally as required by the standard.
 * A comma (or semicolon in `DECIMAL='COMMA'` or `DC` mode) in a
   fixed-width numeric input field terminates the field rather than
   signaling an invalid character error.

>From 346424fcd22259e9a7980cb11353d198bf1f81bd Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Fri, 29 May 2026 12:21:08 -0400
Subject: [PATCH 2/3] clang-format

---
 flang-rt/unittests/Runtime/Namelist.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/flang-rt/unittests/Runtime/Namelist.cpp b/flang-rt/unittests/Runtime/Namelist.cpp
index b81d9ec9d92bc..aaa3c1d354098 100644
--- a/flang-rt/unittests/Runtime/Namelist.cpp
+++ b/flang-rt/unittests/Runtime/Namelist.cpp
@@ -382,8 +382,8 @@ TEST(NamelistTests, BangAsValueSeparator) {
           std::vector<int>{}, std::vector<std::complex<float>>{{0.f, 0.f}})};
   OwningPtr<Descriptor> cDesc{MakeArray<TypeCategory::Character, 1>(
       std::vector<int>{}, std::vector<std::string>{"        "}, 8)};
-  const NamelistGroup::Item items[]{{"i", *iDesc}, {"r", *rDesc},
-      {"l", *lDesc}, {"z", *zDesc}, {"c", *cDesc}};
+  const NamelistGroup::Item items[]{{"i", *iDesc}, {"r", *rDesc}, {"l", *lDesc},
+      {"z", *zDesc}, {"c", *cDesc}};
   const NamelistGroup group{"nml", 5, items};
   // No space before any '!' — the '!' must still terminate each value
   // and start a comment that runs to end of record.  Inside the character

>From f43269300632b0732341535a1fdfef31b006a971 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Wed, 3 Jun 2026 17:48:42 -0400
Subject: [PATCH 3/3] New test based on the code review feedback

---
 flang-rt/unittests/Runtime/ListInputTest.cpp | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/flang-rt/unittests/Runtime/ListInputTest.cpp b/flang-rt/unittests/Runtime/ListInputTest.cpp
index a8f0d4516ceba..be8ead82f3a6a 100644
--- a/flang-rt/unittests/Runtime/ListInputTest.cpp
+++ b/flang-rt/unittests/Runtime/ListInputTest.cpp
@@ -10,6 +10,7 @@
 #include "flang-rt/runtime/descriptor.h"
 #include "flang-rt/runtime/io-error.h"
 #include "flang/Runtime/io-api.h"
+#include "flang/Runtime/iostat-consts.h"
 
 using namespace Fortran::runtime;
 using namespace Fortran::runtime::io;
@@ -151,6 +152,23 @@ TEST(InputTest, TestListInputInvalidFormat) {
       "Bad character 'g' in INTEGER input field");
 }
 
+// The NAMELIST extension that treats '!' as a value separator is scoped to
+// NAMELIST mode only.  In ordinary list-directed input '!' is not a comment
+// introducer, so a '!' glued to a value must still be rejected as an invalid
+// trailing character.
+TEST(InputTest, BangIsNotSeparatorInListDirected) {
+  std::string buffer{"0.01!comment"};
+  auto *cookie{IONAME(BeginInternalListInput)(
+      buffer.data(), buffer.size(), nullptr, 0, __FILE__, __LINE__)};
+  IONAME(EnableHandlers)(cookie, /*hasIoStat=*/true);
+  float got{-1.f};
+  IONAME(InputReal32)(cookie, got);
+  auto status{IONAME(EndIoStatement)(cookie)};
+  ASSERT_EQ(status, IostatBadListDirectedInputSeparator)
+      << "expected '!' to be rejected in list-directed input, got status "
+      << static_cast<int>(status);
+}
+
 using ParamTy = std::tuple<std::string, std::vector<int>>;
 
 struct SimpleListInputTest : testing::TestWithParam<ParamTy> {};



More information about the flang-commits mailing list