diff --git a/flang/docs/Extensions.md b/flang/docs/Extensions.md index 6c6588025a392..ab040b61703c8 100644 --- a/flang/docs/Extensions.md +++ b/flang/docs/Extensions.md @@ -315,6 +315,9 @@ end * When a file included via an `INCLUDE` line or `#include` directive has a continuation marker at the end of its last line in free form, Fortran line continuation works. +* 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 `$`. ### Extensions supported when enabled by options diff --git a/flang/runtime/edit-input.cpp b/flang/runtime/edit-input.cpp index c4fa186e289db..0fa6368ee591c 100644 --- a/flang/runtime/edit-input.cpp +++ b/flang/runtime/edit-input.cpp @@ -21,7 +21,8 @@ namespace Fortran::runtime::io { static inline bool IsCharValueSeparator(const DataEdit &edit, char32_t ch) { char32_t comma{ edit.modes.editingFlags & decimalComma ? char32_t{';'} : char32_t{','}}; - return ch == ' ' || ch == '\t' || ch == '/' || ch == comma; + return ch == ' ' || ch == '\t' || ch == comma || ch == '/' || + (edit.IsNamelist() && (ch == '&' || ch == '$')); } static bool CheckCompleteListDirectedField( @@ -917,6 +918,10 @@ static bool EditListDirectedCharacterInput( case '/': isSep = true; break; + case '&': + case '$': + isSep = edit.IsNamelist(); + break; case ',': isSep = !(edit.modes.editingFlags & decimalComma); break; diff --git a/flang/runtime/io-stmt.cpp b/flang/runtime/io-stmt.cpp index 921c6e625edb5..7052a6acf41ce 100644 --- a/flang/runtime/io-stmt.cpp +++ b/flang/runtime/io-stmt.cpp @@ -580,6 +580,12 @@ std::optional IoStatementState::NextInField( case '*': case '\n': // for stream access return std::nullopt; + case '&': + case '$': + if (edit.IsNamelist()) { + return std::nullopt; + } + break; case ',': if (!(edit.modes.editingFlags & decimalComma)) { return std::nullopt; diff --git a/flang/runtime/namelist.cpp b/flang/runtime/namelist.cpp index 61815a7cc8a40..d9908bf7089ac 100644 --- a/flang/runtime/namelist.cpp +++ b/flang/runtime/namelist.cpp @@ -82,7 +82,7 @@ bool IONAME(OutputNamelist)(Cookie cookie, const NamelistGroup &group) { static constexpr bool IsLegalIdStart(char32_t ch) { return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_' || - ch == '@' || ch == '$'; + ch == '@'; } static constexpr bool IsLegalIdChar(char32_t ch) { @@ -378,12 +378,13 @@ static bool HandleComponent(IoStatementState &io, Descriptor &desc, return false; } -// Advance to the terminal '/' of a namelist group. +// Advance to the terminal '/' of a namelist group or leading '&'/'$' +// of the next. static void SkipNamelistGroup(IoStatementState &io) { std::size_t byteCount{0}; while (auto ch{io.GetNextNonBlank(byteCount)}) { io.HandleRelativePosition(byteCount); - if (*ch == '/') { + if (*ch == '/' || *ch == '&' || *ch == '$') { break; } else if (*ch == '\'' || *ch == '"') { // Skip quoted character literal @@ -418,7 +419,7 @@ bool IONAME(InputNamelist)(Cookie cookie, const NamelistGroup &group) { std::size_t byteCount{0}; while (true) { next = io.GetNextNonBlank(byteCount); - while (next && *next != '&') { + while (next && *next != '&' && *next != '$') { // Extension: comment lines without ! before namelist groups if (!io.AdvanceRecord()) { next.reset(); @@ -430,9 +431,10 @@ bool IONAME(InputNamelist)(Cookie cookie, const NamelistGroup &group) { handler.SignalEnd(); return false; } - if (*next != '&') { + if (*next != '&' && *next != '$') { handler.SignalError( - "NAMELIST input group does not begin with '&' (at '%lc')", *next); + "NAMELIST input group does not begin with '&' or '$' (at '%lc')", + *next); return false; } io.HandleRelativePosition(byteCount); @@ -448,7 +450,7 @@ bool IONAME(InputNamelist)(Cookie cookie, const NamelistGroup &group) { // Read the group's items while (true) { next = io.GetNextNonBlank(byteCount); - if (!next || *next == '/') { + if (!next || *next == '/' || *next == '&' || *next == '$') { break; } if (!GetLowerCaseName(io, name, sizeof name)) { @@ -540,12 +542,15 @@ bool IONAME(InputNamelist)(Cookie cookie, const NamelistGroup &group) { io.HandleRelativePosition(byteCount); } } - if (!next || *next != '/') { + if (next && *next == '/') { + io.HandleRelativePosition(byteCount); + } else if (*next && (*next == '&' || *next == '$')) { + // stop at beginning of next group + } else { handler.SignalError( "No '/' found after NAMELIST group '%s'", group.groupName); return false; } - io.HandleRelativePosition(byteCount); return true; } @@ -565,7 +570,7 @@ bool IsNamelistNameOrSlash(IoStatementState &io) { // TODO: how to deal with NaN(...) ambiguity? return ch && (*ch == '=' || *ch == '(' || *ch == '%'); } else { - return *ch == '/'; + return *ch == '/' || *ch == '&' || *ch == '$'; } } }