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

via flang-commits flang-commits at lists.llvm.org
Thu Jun 4 09:06:31 PDT 2026


Author: Eugene Epshteyn
Date: 2026-06-04T12:06:26-04:00
New Revision: 01dbfa0b6e04c09e389bafd7668b2f99bba3ea79

URL: https://github.com/llvm/llvm-project/commit/01dbfa0b6e04c09e389bafd7668b2f99bba3ea79
DIFF: https://github.com/llvm/llvm-project/commit/01dbfa0b6e04c09e389bafd7668b2f99bba3ea79.diff

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

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

Added: 
    

Modified: 
    flang-rt/lib/runtime/edit-input.cpp
    flang-rt/lib/runtime/io-stmt.cpp
    flang-rt/unittests/Runtime/ListInputTest.cpp
    flang-rt/unittests/Runtime/Namelist.cpp
    flang/docs/Extensions.md

Removed: 
    


################################################################################
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/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> {};

diff  --git a/flang-rt/unittests/Runtime/Namelist.cpp b/flang-rt/unittests/Runtime/Namelist.cpp
index f190bea14acfe..aaa3c1d354098 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 12fdc4d4945cb..fed4ebba2943e 100644
--- a/flang/docs/Extensions.md
+++ b/flang/docs/Extensions.md
@@ -450,6 +450,15 @@ print *, is_contiguous(a(::2))                   ! prints T in Flang
 * 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.


        


More information about the flang-commits mailing list