[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