[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