diff --git a/flang-rt/include/flang-rt/runtime/io-error.h b/flang-rt/include/flang-rt/runtime/io-error.h index 3e8401036f289..0ac1183131808 100644 --- a/flang-rt/include/flang-rt/runtime/io-error.h +++ b/flang-rt/include/flang-rt/runtime/io-error.h @@ -67,6 +67,17 @@ class IoErrorHandler : public Terminator { RT_API_ATTRS int GetIoStat() const { return ioStat_; } RT_API_ATTRS bool GetIoMsg(char *, std::size_t); + // Sets the HasEnd flag so that EOF isn't fatal; used to peek ahead + RT_API_ATTRS bool SetHasEnd(bool yes = true) { + bool oldValue{(flags_ & hasEnd) != 0}; + if (yes) { + flags_ |= hasEnd; + } else { + flags_ &= ~hasEnd; + } + return oldValue; + } + private: enum Flag : std::uint8_t { hasIoStat = 1, // IOSTAT= diff --git a/flang-rt/include/flang-rt/runtime/io-stmt.h b/flang-rt/include/flang-rt/runtime/io-stmt.h index 1c4f06c0a7082..1cb72d87d3dfa 100644 --- a/flang-rt/include/flang-rt/runtime/io-stmt.h +++ b/flang-rt/include/flang-rt/runtime/io-stmt.h @@ -703,6 +703,13 @@ class ChildListIoStatementState : public ChildIoStatementState, using ListDirectedStatementState::GetNextDataEdit; RT_API_ATTRS bool AdvanceRecord(int = 1); RT_API_ATTRS int EndIoStatement(); + RT_API_ATTRS bool CanAdvance() { + return DIR == Direction::Input && + (canAdvance_ || this->mutableModes().inNamelist); + } + +private: + bool canAdvance_{false}; }; template diff --git a/flang-rt/lib/runtime/descriptor-io.cpp b/flang-rt/lib/runtime/descriptor-io.cpp index e00072510aff7..42ac4c0516637 100644 --- a/flang-rt/lib/runtime/descriptor-io.cpp +++ b/flang-rt/lib/runtime/descriptor-io.cpp @@ -47,9 +47,11 @@ static RT_API_ATTRS common::optional DefinedFormattedIo( const typeInfo::DerivedType &derived, const typeInfo::SpecialBinding &special, const SubscriptValue subscripts[]) { - // Look at the next data edit descriptor. If this is list-directed I/O, the - // "maxRepeat=0" argument will prevent the input from advancing over an + // Look at the next data edit descriptor. If this is list-directed input, + // the "maxRepeat=0" argument will prevent the input from advancing over an // initial '(' that shouldn't be consumed now as the start of a real part. + // It also allows reaching EOF without crashing, since the EOF only matters + // if a child READ is actually performed. common::optional peek{io.GetNextDataEdit(/*maxRepeat=*/0)}; if (peek && (peek->descriptor == DataEdit::DefinedDerivedType || diff --git a/flang-rt/lib/runtime/edit-input.cpp b/flang-rt/lib/runtime/edit-input.cpp index 6ab546ee59f74..436fc3894d902 100644 --- a/flang-rt/lib/runtime/edit-input.cpp +++ b/flang-rt/lib/runtime/edit-input.cpp @@ -53,11 +53,13 @@ static RT_API_ATTRS bool EditBOZInput( IoStatementState &io, const DataEdit &edit, void *n, std::size_t bytes) { // Skip leading white space & zeroes common::optional remaining{io.CueUpInput(edit)}; - auto start{io.GetConnectionState().positionInRecord}; + const ConnectionState &connection{io.GetConnectionState()}; + auto leftTabLimit{connection.leftTabLimit.value_or(0)}; + auto start{connection.positionInRecord - leftTabLimit}; common::optional next{io.NextInField(remaining, edit)}; if (next.value_or('?') == '0') { do { - start = io.GetConnectionState().positionInRecord; + start = connection.positionInRecord - leftTabLimit; next = io.NextInField(remaining, edit); } while (next && *next == '0'); } @@ -447,7 +449,9 @@ static RT_API_ATTRS ScannedRealInput ScanRealInput( } // In list-directed input, a bad exponent is not consumed. auto nextBeforeExponent{next}; - auto startExponent{io.GetConnectionState().positionInRecord}; + const ConnectionState &connection{io.GetConnectionState()}; + auto leftTabLimit{connection.leftTabLimit.value_or(0)}; + auto startExponent{connection.positionInRecord - leftTabLimit}; bool hasGoodExponent{false}; if (next) { if (isHexadecimal) { diff --git a/flang-rt/lib/runtime/io-stmt.cpp b/flang-rt/lib/runtime/io-stmt.cpp index 7bcba5fe71ee4..b958f23cf5342 100644 --- a/flang-rt/lib/runtime/io-stmt.cpp +++ b/flang-rt/lib/runtime/io-stmt.cpp @@ -880,6 +880,9 @@ ListDirectedStatementState::GetNextDataEdit( edit.descriptor = DataEdit::ListDirectedImaginaryPart; } auto fastField{io.GetUpcomingFastAsciiField()}; + // Reaching EOF is okay when peeking at list-directed defined input; + // pretend that there's an END= in that case. + bool oldHasEnd{maxRepeat == 0 && !io.GetIoErrorHandler().SetHasEnd()}; auto ch{io.GetNextNonBlank(byteCount, &fastField)}; if (ch && *ch == comma && eatComma_) { // Consume comma & whitespace after previous item. @@ -890,19 +893,23 @@ ListDirectedStatementState::GetNextDataEdit( ch = io.GetNextNonBlank(byteCount, &fastField); } eatComma_ = true; - if (!ch) { - return common::nullopt; + if (maxRepeat == 0 && !oldHasEnd) { + io.GetIoErrorHandler().SetHasEnd(false); } - if (*ch == '/') { + if (!ch) { // EOF + if (maxRepeat == 0) { + return edit; // DataEdit::ListDirected for look-ahead + } else { + return common::nullopt; + } + } else if (*ch == '/') { hitSlash_ = true; edit.descriptor = DataEdit::ListDirectedNullValue; return edit; - } - if (*ch == comma) { // separator: null value + } else if (*ch == comma) { // separator: null value edit.descriptor = DataEdit::ListDirectedNullValue; return edit; - } - if (imaginaryPart_) { // can't repeat components + } else if (imaginaryPart_) { // can't repeat components return edit; } if (*ch >= '0' && *ch <= '9' && fastField.MightBeRepetitionCount()) { @@ -1103,10 +1110,19 @@ ChildListIoStatementState::ChildListIoStatementState( : ChildIoStatementState{child, sourceFile, sourceLine} { #if !defined(RT_DEVICE_AVOID_RECURSION) if constexpr (DIR == Direction::Input) { - if (auto *listInput{child.parent() + if (const auto *listInput{child.parent() .get_if>()}) { this->set_eatComma(listInput->eatComma()); this->namelistGroup_ = listInput->namelistGroup(); + if (auto *childListInput{child.parent() + .get_if>()}) { + // Child list input whose parent is child list input: can advance + // if the parent can. + this->canAdvance_ = childListInput->CanAdvance(); + } else { + // Child list input of top-level list input: can advance. + this->canAdvance_ = true; + } } } #else @@ -1117,12 +1133,7 @@ ChildListIoStatementState::ChildListIoStatementState( template bool ChildListIoStatementState::AdvanceRecord(int n) { #if !defined(RT_DEVICE_AVOID_RECURSION) - // Allow child NAMELIST input to advance - if (DIR == Direction::Input && this->mutableModes().inNamelist) { - return this->child().parent().AdvanceRecord(n); - } else { - return false; - } + return this->CanAdvance() && this->child().parent().AdvanceRecord(n); #else this->ReportUnsupportedChildIo(); #endif diff --git a/flang/docs/Extensions.md b/flang/docs/Extensions.md index c442a9cd6859e..7a2e2a7d2a7aa 100644 --- a/flang/docs/Extensions.md +++ b/flang/docs/Extensions.md @@ -917,6 +917,17 @@ print *, [(j,j=1,10)] and the portable interpretation across the most common Fortran compilers. +* `NAMELIST` child input statements are allowed to advance to further + input records. + Further, advancement is allowed when the parent input statement is + a non-child (top level) list-directed input statement, or, recursively, + an intermediate child list-directed input statement that can advance. + This means that non-`NAMELIST` list-directed child input statements are + not allowed to advance when they have an ancestor formatted input statement + that is not list-directed and there is no intervening `NAMELIST`. + This design allows format-driven input with `DT` editing to retain + control over advancement in child input, while otherwise allowing it. + ## De Facto Standard Features * `EXTENDS_TYPE_OF()` returns `.TRUE.` if both of its arguments have the