diff --git a/flang/include/flang/Runtime/iostat.h b/flang/include/flang/Runtime/iostat.h --- a/flang/include/flang/Runtime/iostat.h +++ b/flang/include/flang/Runtime/iostat.h @@ -45,7 +45,7 @@ IostatInternalWriteOverrun, IostatErrorInFormat, IostatErrorInKeyword, - IostatEndfileNonSequential, + IostatEndfileDirect, IostatEndfileUnwritable, IostatOpenBadRecl, IostatOpenUnknownSize, diff --git a/flang/runtime/connection.h b/flang/runtime/connection.h --- a/flang/runtime/connection.h +++ b/flang/runtime/connection.h @@ -22,8 +22,6 @@ enum class Direction { Output, Input }; enum class Access { Sequential, Direct, Stream }; -inline bool IsRecordFile(Access a) { return a != Access::Stream; } - // These characteristics of a connection are immutable after being // established in an OPEN statement. struct ConnectionAttributes { @@ -31,6 +29,11 @@ std::optional isUnformatted; // FORM='UNFORMATTED' if true bool isUTF8{false}; // ENCODING='UTF-8' std::optional openRecl; // RECL= on OPEN + + bool IsRecordFile() const { + // Formatted stream files are viewed as having records, at least on input + return access != Access::Stream || !isUnformatted.value_or(true); + } }; struct ConnectionState : public ConnectionAttributes { diff --git a/flang/runtime/edit-input.cpp b/flang/runtime/edit-input.cpp --- a/flang/runtime/edit-input.cpp +++ b/flang/runtime/edit-input.cpp @@ -19,7 +19,7 @@ std::optional remaining; std::optional next{io.PrepareInput(edit, remaining)}; common::UnsignedInt128 value{0}; - for (; next; next = io.NextInField(remaining)) { + for (; next; next = io.NextInField(remaining, edit)) { char32_t ch{*next}; if (ch == ' ' || ch == '\t') { continue; @@ -63,7 +63,7 @@ if (negative || *next == '+') { io.GotChar(); io.SkipSpaces(remaining); - next = io.NextInField(remaining, GetDecimalPoint(edit)); + next = io.NextInField(remaining, edit); } } return negative; @@ -101,7 +101,7 @@ bool negate{ScanNumericPrefix(io, edit, next, remaining)}; common::UnsignedInt128 value{0}; bool any{negate}; - for (; next; next = io.NextInField(remaining)) { + for (; next; next = io.NextInField(remaining, edit)) { char32_t ch{*next}; if (ch == ' ' || ch == '\t') { if (edit.modes.editingFlags & blankZero) { @@ -167,7 +167,7 @@ // Subtle: a blank field of digits could be followed by 'E' or 'D', for (; next && ((*next >= 'a' && *next <= 'z') || (*next >= 'A' && *next <= 'Z')); - next = io.NextInField(remaining)) { + next = io.NextInField(remaining, edit)) { if (*next >= 'a' && *next <= 'z') { Put(*next - 'a' + 'A'); } else { @@ -176,7 +176,7 @@ } if (next && *next == '(') { // NaN(...) while (next && *next != ')') { - next = io.NextInField(remaining); + next = io.NextInField(remaining, edit); } } exponent = 0; @@ -185,7 +185,7 @@ Put('.'); // input field is normalized to a fraction auto start{got}; bool bzMode{(edit.modes.editingFlags & blankZero) != 0}; - for (; next; next = io.NextInField(remaining, decimal)) { + for (; next; next = io.NextInField(remaining, edit)) { char32_t ch{*next}; if (ch == ' ' || ch == '\t') { if (bzMode) { @@ -214,7 +214,7 @@ // Optional exponent letter. Blanks are allowed between the // optional exponent letter and the exponent value. io.SkipSpaces(remaining); - next = io.NextInField(remaining); + next = io.NextInField(remaining, edit); } // The default exponent is -kP, but the scale factor doesn't affect // an explicit exponent. @@ -224,9 +224,9 @@ (bzMode && (*next == ' ' || *next == '\t')))) { bool negExpo{*next == '-'}; if (negExpo || *next == '+') { - next = io.NextInField(remaining); + next = io.NextInField(remaining, edit); } - for (exponent = 0; next; next = io.NextInField(remaining)) { + for (exponent = 0; next; next = io.NextInField(remaining, edit)) { if (*next >= '0' && *next <= '9') { exponent = 10 * exponent + *next - '0'; } else if (bzMode && (*next == ' ' || *next == '\t')) { @@ -257,7 +257,7 @@ // input value. if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) { if (next && (*next == ' ' || *next == '\t')) { - next = io.NextInField(remaining); + next = io.NextInField(remaining, edit); } if (!next) { // NextInField fails on separators like ')' next = io.GetCurrentChar(); @@ -267,7 +267,7 @@ } } else if (remaining) { while (next && (*next == ' ' || *next == '\t')) { - next = io.NextInField(remaining); + next = io.NextInField(remaining, edit); } if (next) { return 0; // error: unused nonblank character in fixed-width field @@ -457,7 +457,7 @@ std::optional remaining; std::optional next{io.PrepareInput(edit, remaining)}; if (next && *next == '.') { // skip optional period - next = io.NextInField(remaining); + next = io.NextInField(remaining, edit); } if (!next) { io.GetIoErrorHandler().SignalError("Empty LOGICAL input field"); @@ -480,7 +480,7 @@ if (remaining) { // ignore the rest of the field io.HandleRelativePosition(*remaining); } else if (edit.descriptor == DataEdit::ListDirected) { - while (io.NextInField(remaining)) { // discard rest of field + while (io.NextInField(remaining, edit)) { // discard rest of field } } return true; @@ -520,7 +520,7 @@ } static bool EditListDirectedDefaultCharacterInput( - IoStatementState &io, char *x, std::size_t length) { + IoStatementState &io, char *x, std::size_t length, const DataEdit &edit) { auto ch{io.GetCurrentChar()}; if (ch && (*ch == '\'' || *ch == '"')) { io.HandleRelativePosition(1); @@ -532,8 +532,7 @@ // Undelimited list-directed character input: stop at a value separator // or the end of the current record. std::optional remaining{length}; - for (std::optional next{io.NextInField(remaining)}; next; - next = io.NextInField(remaining)) { + while (std::optional next{io.NextInField(remaining, edit)}) { switch (*next) { case ' ': case '\t': @@ -555,7 +554,7 @@ IoStatementState &io, const DataEdit &edit, char *x, std::size_t length) { switch (edit.descriptor) { case DataEdit::ListDirected: - return EditListDirectedDefaultCharacterInput(io, x, length); + return EditListDirectedDefaultCharacterInput(io, x, length, edit); case 'A': case 'G': break; @@ -576,8 +575,7 @@ // characters. When the variable is wider than the field, there's // trailing padding. std::int64_t skip{*remaining - static_cast(length)}; - for (std::optional next{io.NextInField(remaining)}; next; - next = io.NextInField(remaining)) { + while (std::optional next{io.NextInField(remaining, edit)}) { if (skip > 0) { --skip; io.GotChar(-1); diff --git a/flang/runtime/file.h b/flang/runtime/file.h --- a/flang/runtime/file.h +++ b/flang/runtime/file.h @@ -35,8 +35,8 @@ bool mayPosition() const { return mayPosition_; } bool mayAsynchronous() const { return mayAsynchronous_; } void set_mayAsynchronous(bool yes) { mayAsynchronous_ = yes; } - FileOffset position() const { return position_; } bool isTerminal() const { return isTerminal_; } + bool isWindowsTextFile() const { return isWindowsTextFile_; } std::optional knownSize() const { return knownSize_; } bool IsConnected() const { return fd_ >= 0; } @@ -98,6 +98,7 @@ FileOffset position_{0}; std::optional knownSize_; bool isTerminal_{false}; + bool isWindowsTextFile_{false}; // expands LF to CR+LF on write int nextId_; OwningPtr pending_; diff --git a/flang/runtime/file.cpp b/flang/runtime/file.cpp --- a/flang/runtime/file.cpp +++ b/flang/runtime/file.cpp @@ -45,8 +45,8 @@ if (::GetTempFileNameA(tempDirName, "Fortran", uUnique, tempFileName) == 0) { return -1; } - int fd{::_open( - tempFileName, _O_CREAT | _O_TEMPORARY | _O_RDWR, _S_IREAD | _S_IWRITE)}; + int fd{::_open(tempFileName, _O_CREAT | _O_BINARY | _O_TEMPORARY | _O_RDWR, + _S_IREAD | _S_IWRITE)}; #else char path[]{"/tmp/Fortran-Scratch-XXXXXX"}; int fd{::mkstemp(path)}; @@ -82,6 +82,12 @@ return; } int flags{0}; +#ifdef _WIN32 + // We emit explicit CR+LF line endings and cope with them on input + // for formatted files, since we can't yet always know now at OPEN + // time whether the file is formatted or not. + flags |= O_BINARY; +#endif if (status != OpenStatus::Old) { flags |= O_CREAT; } @@ -154,6 +160,9 @@ mayRead_ = fd == 0; mayWrite_ = fd != 0; mayPosition_ = false; +#ifdef _WIN32 + isWindowsTextFile_ = true; +#endif } void OpenFile::Close(CloseStatus status, IoErrorHandler &handler) { diff --git a/flang/runtime/io-api.cpp b/flang/runtime/io-api.cpp --- a/flang/runtime/io-api.cpp +++ b/flang/runtime/io-api.cpp @@ -524,18 +524,17 @@ bool IONAME(SetPos)(Cookie cookie, std::int64_t pos) { IoStatementState &io{*cookie}; ConnectionState &connection{io.GetConnectionState()}; + IoErrorHandler &handler{io.GetIoErrorHandler()}; if (connection.access != Access::Stream) { - io.GetIoErrorHandler().SignalError( - "POS= may not appear unless ACCESS='STREAM'"); + handler.SignalError("POS= may not appear unless ACCESS='STREAM'"); return false; } - if (pos < 1) { - io.GetIoErrorHandler().SignalError( - "POS=%zd is invalid", static_cast(pos)); + if (pos < 1) { // POS=1 is beginning of file (12.6.2.11) + handler.SignalError("POS=%zd is invalid", static_cast(pos)); return false; } if (auto *unit{io.GetExternalFileUnit()}) { - unit->SetPosition(pos); + unit->SetPosition(pos - 1, handler); return true; } io.GetIoErrorHandler().Crash("SetPos() on internal unit"); @@ -545,23 +544,22 @@ bool IONAME(SetRec)(Cookie cookie, std::int64_t rec) { IoStatementState &io{*cookie}; ConnectionState &connection{io.GetConnectionState()}; + IoErrorHandler &handler{io.GetIoErrorHandler()}; if (connection.access != Access::Direct) { - io.GetIoErrorHandler().SignalError( - "REC= may not appear unless ACCESS='DIRECT'"); + handler.SignalError("REC= may not appear unless ACCESS='DIRECT'"); return false; } if (!connection.openRecl) { - io.GetIoErrorHandler().SignalError("RECL= was not specified"); + handler.SignalError("RECL= was not specified"); return false; } if (rec < 1) { - io.GetIoErrorHandler().SignalError( - "REC=%zd is invalid", static_cast(rec)); + handler.SignalError("REC=%zd is invalid", static_cast(rec)); return false; } connection.currentRecordNumber = rec; if (auto *unit{io.GetExternalFileUnit()}) { - unit->SetPosition((rec - 1) * *connection.openRecl); + unit->SetPosition((rec - 1) * *connection.openRecl, handler); } return true; } diff --git a/flang/runtime/io-stmt.h b/flang/runtime/io-stmt.h --- a/flang/runtime/io-stmt.h +++ b/flang/runtime/io-stmt.h @@ -142,7 +142,7 @@ } SkipSpaces(remaining); } - return NextInField(remaining); + return NextInField(remaining, edit); } std::optional SkipSpaces(std::optional &remaining) { @@ -163,59 +163,10 @@ return std::nullopt; } + // Acquires the next input character, respecting any applicable field width + // or separator character. std::optional NextInField( - std::optional &remaining, char32_t decimal = '.') { - if (!remaining) { // list-directed or NAMELIST: check for separators - if (auto next{GetCurrentChar()}) { - if (*next == decimal) { // can be ',' - HandleRelativePosition(1); - return next; - } - switch (*next) { - case ' ': - case '\t': - case ',': - case ';': - case '/': - case '(': - case ')': - case '\'': - case '"': - case '*': - case '\n': // for stream access - break; - default: - HandleRelativePosition(1); - return next; - } - } - } else if (*remaining > 0) { - if (auto next{GetCurrentChar()}) { - --*remaining; - HandleRelativePosition(1); - GotChar(); - return next; - } - const ConnectionState &connection{GetConnectionState()}; - if (!connection.IsAtEOF()) { - if (auto length{connection.EffectiveRecordLength()}) { - if (connection.positionInRecord >= *length) { - IoErrorHandler &handler{GetIoErrorHandler()}; - if (mutableModes().nonAdvancing) { - handler.SignalEor(); - } else if (connection.openRecl && !connection.modes.pad) { - handler.SignalError(IostatRecordReadOverrun); - } - if (connection.modes.pad) { // PAD='YES' - --*remaining; - return std::optional{' '}; - } - } - } - } - } - return std::nullopt; - } + std::optional &remaining, const DataEdit &); // Skips spaces, advances records, and ignores NAMELIST comments std::optional GetNextNonBlank() { diff --git a/flang/runtime/io-stmt.cpp b/flang/runtime/io-stmt.cpp --- a/flang/runtime/io-stmt.cpp +++ b/flang/runtime/io-stmt.cpp @@ -268,7 +268,7 @@ : ExternalIoStatementBase{unit, sourceFile, sourceLine}, mutableModes_{ unit.modes} { if constexpr (DIR == Direction::Output) { - // If the last statement was a non advancing IO input statement, the unit + // If the last statement was a non-advancing IO input statement, the unit // furthestPositionInRecord was not advanced, but the positionInRecord may // have been advanced. Advance furthestPositionInRecord here to avoid // overwriting the part of the record that has been read with blanks. @@ -505,6 +505,66 @@ } } +std::optional IoStatementState::NextInField( + std::optional &remaining, const DataEdit &edit) { + if (!remaining) { // Stream, list-directed, or NAMELIST + if (auto next{GetCurrentChar()}) { + if (edit.IsListDirected()) { + // list-directed or NAMELIST: check for separators + switch (*next) { + case ' ': + case '\t': + case ';': + case '/': + case '(': + case ')': + case '\'': + case '"': + case '*': + case '\n': // for stream access + return std::nullopt; + case ',': + if (edit.modes.editingFlags & decimalComma) { + break; + } else { + return std::nullopt; + } + default: + break; + } + } + HandleRelativePosition(1); + GotChar(); + return next; + } + } else if (*remaining > 0) { + if (auto next{GetCurrentChar()}) { + --*remaining; + HandleRelativePosition(1); + GotChar(); + return next; + } + const ConnectionState &connection{GetConnectionState()}; + if (!connection.IsAtEOF()) { + if (auto length{connection.EffectiveRecordLength()}) { + if (connection.positionInRecord >= *length) { + IoErrorHandler &handler{GetIoErrorHandler()}; + if (mutableModes().nonAdvancing) { + handler.SignalEor(); + } else if (connection.openRecl && !connection.modes.pad) { + handler.SignalError(IostatRecordReadOverrun); + } + if (connection.modes.pad) { // PAD='YES' + --*remaining; + return std::optional{' '}; + } + } + } + } + } + return std::nullopt; +} + bool IoStatementState::Inquire( InquiryKeywordHash inquiry, char *out, std::size_t chars) { return std::visit( @@ -1060,7 +1120,7 @@ result = unit().IsConnected() ? unit().unitNumber() : -1; return true; case HashInquiryKeyword("POS"): - result = unit().position(); + result = unit().InquirePos(); return true; case HashInquiryKeyword("RECL"): if (!unit().IsConnected()) { diff --git a/flang/runtime/iostat.cpp b/flang/runtime/iostat.cpp --- a/flang/runtime/iostat.cpp +++ b/flang/runtime/iostat.cpp @@ -33,8 +33,8 @@ return "Invalid FORMAT"; case IostatErrorInKeyword: return "Bad keyword argument value"; - case IostatEndfileNonSequential: - return "ENDFILE on non-sequential file"; + case IostatEndfileDirect: + return "ENDFILE on direct-access file"; case IostatEndfileUnwritable: return "ENDFILE on read-only file"; case IostatOpenBadRecl: diff --git a/flang/runtime/unit.h b/flang/runtime/unit.h --- a/flang/runtime/unit.h +++ b/flang/runtime/unit.h @@ -90,10 +90,10 @@ void Endfile(IoErrorHandler &); void Rewind(IoErrorHandler &); void EndIoStatement(); - void SetPosition(std::int64_t pos) { - frameOffsetInFile_ = pos; - recordOffsetInFrame_ = 0; - BeginRecord(); + void SetPosition(std::int64_t, IoErrorHandler &); // zero-based + std::int64_t InquirePos() const { + // 12.6.2.11 defines POS=1 as the beginning of file + return frameOffsetInFile_ + 1; } ChildIo *GetChildIo() { return child_.get(); } @@ -104,18 +104,18 @@ static UnitMap &GetUnitMap(); const char *FrameNextInput(IoErrorHandler &, std::size_t); void BeginSequentialVariableUnformattedInputRecord(IoErrorHandler &); - void BeginSequentialVariableFormattedInputRecord(IoErrorHandler &); + void BeginVariableFormattedInputRecord(IoErrorHandler &); void BackspaceFixedRecord(IoErrorHandler &); void BackspaceVariableUnformattedRecord(IoErrorHandler &); void BackspaceVariableFormattedRecord(IoErrorHandler &); - bool SetSequentialVariableFormattedRecordLength(); + bool SetVariableFormattedRecordLength(); void DoImpliedEndfile(IoErrorHandler &); void DoEndfile(IoErrorHandler &); void CommitWrites(); int unitNumber_{-1}; Direction direction_{Direction::Output}; - bool impliedEndfile_{false}; // seq. output has taken place + bool impliedEndfile_{false}; // sequential/stream output has taken place bool beganReadingRecord_{false}; Lock lock_; diff --git a/flang/runtime/unit.cpp b/flang/runtime/unit.cpp --- a/flang/runtime/unit.cpp +++ b/flang/runtime/unit.cpp @@ -151,7 +151,7 @@ if (totalBytes && access == Access::Direct && openRecl.value_or(0) > 0) { endfileRecordNumber = 1 + (*totalBytes / *openRecl); } - if (position == Position::Append) { + if (position == Position::Append && access != Access::Stream) { if (!endfileRecordNumber) { // Fake it so that we can backspace relative from the end endfileRecordNumber = std::numeric_limits::max() - 2; @@ -288,7 +288,12 @@ header = static_cast(sizeof(std::uint32_t)); extra = 2 * header; } else { - extra = 1; // newline +#ifdef _WIN32 + if (!isWindowsTextFile()) { + ++extra; // carriage return (CR) + } +#endif + ++extra; // newline (LF) } } if (furthestAfter > extra + *openRecl) { @@ -348,8 +353,10 @@ furthestPositionInRecord = furthestAfter; return true; } else { - // EOF or error: can be handled & has been signaled - endfileRecordNumber = currentRecordNumber; + handler.SignalEnd(); + if (access == Access::Sequential) { + endfileRecordNumber = currentRecordNumber; + } return false; } } @@ -357,10 +364,17 @@ std::size_t ExternalFileUnit::GetNextInputBytes( const char *&p, IoErrorHandler &handler) { RUNTIME_CHECK(handler, direction_ == Direction::Input); - p = FrameNextInput(handler, 1); - return p ? EffectiveRecordLength().value_or(positionInRecord + 1) - - positionInRecord - : 0; + std::size_t length{1}; + if (auto recl{EffectiveRecordLength()}) { + if (positionInRecord < *recl) { + length = *recl - positionInRecord; + } else { + p = nullptr; + return 0; + } + } + p = FrameNextInput(handler, length); + return p ? length : 0; } std::optional ExternalFileUnit::GetCurrentChar( @@ -383,18 +397,20 @@ auto at{recordOffsetInFrame_ + positionInRecord}; auto need{static_cast(at + bytes)}; auto got{ReadFrame(frameOffsetInFile_, need, handler)}; - SetSequentialVariableFormattedRecordLength(); + SetVariableFormattedRecordLength(); if (got >= need) { return Frame() + at; } handler.SignalEnd(); - endfileRecordNumber = currentRecordNumber; + if (access == Access::Sequential) { + endfileRecordNumber = currentRecordNumber; + } } return nullptr; } -bool ExternalFileUnit::SetSequentialVariableFormattedRecordLength() { - if (recordLength || access != Access::Sequential) { +bool ExternalFileUnit::SetVariableFormattedRecordLength() { + if (recordLength || access == Access::Direct) { return true; } else if (FrameLength() > recordOffsetInFrame_) { const char *record{Frame() + recordOffsetInFrame_}; @@ -430,22 +446,24 @@ recordLength.reset(); handler.SignalEnd(); } - } else if (access == Access::Sequential) { + } else { recordLength.reset(); if (IsAtEOF()) { handler.SignalEnd(); } else { RUNTIME_CHECK(handler, isUnformatted.has_value()); - if (isUnformatted.value_or(false)) { - BeginSequentialVariableUnformattedInputRecord(handler); - } else { // formatted - BeginSequentialVariableFormattedInputRecord(handler); + if (*isUnformatted) { + if (access == Access::Sequential) { + BeginSequentialVariableUnformattedInputRecord(handler); + } + } else { // formatted sequential or stream + BeginVariableFormattedInputRecord(handler); } } } } RUNTIME_CHECK(handler, - recordLength.has_value() || !IsRecordFile(access) || handler.InError()); + recordLength.has_value() || !IsRecordFile() || handler.InError()); return !handler.InError(); } @@ -453,14 +471,15 @@ RUNTIME_CHECK(handler, direction_ == Direction::Input && beganReadingRecord_); beganReadingRecord_ = false; if (handler.InError() && handler.GetIoStat() != IostatEor) { - // avoid bogus crashes in END/ERR circumstances - } else if (access == Access::Sequential) { + // Avoid bogus crashes in END/ERR circumstances; but + // still increment the current record number so that + // an attempted read of an endfile record, followed by + // a BACKSPACE, will still be at EOF. + ++currentRecordNumber; + } else if (IsRecordFile()) { RUNTIME_CHECK(handler, recordLength.has_value()); recordOffsetInFrame_ += *recordLength; - if (openRecl && access == Access::Direct) { - frameOffsetInFile_ += recordOffsetInFrame_; - recordOffsetInFrame_ = 0; - } else { + if (access != Access::Direct) { RUNTIME_CHECK(handler, isUnformatted.has_value()); recordLength.reset(); if (isUnformatted.value_or(false)) { @@ -472,7 +491,7 @@ Frame()[recordOffsetInFrame_] == '\r') { ++recordOffsetInFrame_; } - if (FrameLength() >= recordOffsetInFrame_ && + if (FrameLength() > recordOffsetInFrame_ && Frame()[recordOffsetInFrame_] == '\n') { ++recordOffsetInFrame_; } @@ -482,8 +501,12 @@ } } } + ++currentRecordNumber; + } else { // unformatted stream + furthestPositionInRecord = + std::max(furthestPositionInRecord, positionInRecord); + frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord; } - ++currentRecordNumber; BeginRecord(); } @@ -494,8 +517,10 @@ } else { // Direction::Output bool ok{true}; RUNTIME_CHECK(handler, isUnformatted.has_value()); - if (openRecl && access == Access::Direct) { - if (furthestPositionInRecord < *openRecl) { + positionInRecord = furthestPositionInRecord; + if (access == Access::Direct) { + if (furthestPositionInRecord < + openRecl.value_or(furthestPositionInRecord)) { // Pad remainder of fixed length record WriteFrame( frameOffsetInFile_, recordOffsetInFrame_ + *openRecl, handler); @@ -504,9 +529,8 @@ *openRecl - furthestPositionInRecord); furthestPositionInRecord = *openRecl; } - } else { - positionInRecord = furthestPositionInRecord; - if (isUnformatted.value_or(false)) { + } else if (*isUnformatted) { + if (access == Access::Sequential) { // Append the length of a sequential unformatted variable-length record // as its footer, then overwrite the reserved first four bytes of the // record with its length as its header. These four bytes were skipped @@ -523,27 +547,40 @@ Emit(reinterpret_cast(&length), sizeof length, sizeof length, handler); } else { - // Terminate formatted variable length record - ok = ok && Emit("\n", 1, 1, handler); // TODO: Windows CR+LF + // Unformatted stream: nothing to do } + } else { + // Terminate formatted variable length record + const char *lineEnding{"\n"}; + std::size_t lineEndingBytes{1}; +#ifdef _WIN32 + if (!isWindowsTextFile()) { + lineEnding = "\r\n"; + lineEndingBytes = 2; + } +#endif + ok = ok && Emit(lineEnding, lineEndingBytes, 1, handler); } if (IsAfterEndfile()) { return false; } CommitWrites(); - impliedEndfile_ = true; ++currentRecordNumber; - if (IsAtEOF()) { - endfileRecordNumber.reset(); + if (access != Access::Direct) { + impliedEndfile_ = IsRecordFile(); + if (IsAtEOF()) { + endfileRecordNumber.reset(); + } } return ok; } } void ExternalFileUnit::BackspaceRecord(IoErrorHandler &handler) { - if (access != Access::Sequential) { + if (access == Access::Direct || !IsRecordFile()) { handler.SignalError(IostatBackspaceNonSequential, - "BACKSPACE(UNIT=%d) on non-sequential file", unitNumber()); + "BACKSPACE(UNIT=%d) on direct-access file or unformatted stream", + unitNumber()); } else { if (IsAfterEndfile()) { // BACKSPACE after explicit ENDFILE @@ -590,9 +627,9 @@ } void ExternalFileUnit::Endfile(IoErrorHandler &handler) { - if (access != Access::Sequential) { - handler.SignalError(IostatEndfileNonSequential, - "ENDFILE(UNIT=%d) on non-sequential file", unitNumber()); + if (access == Access::Direct) { + handler.SignalError(IostatEndfileDirect, + "ENDFILE(UNIT=%d) on direct-access file", unitNumber()); } else if (!mayWrite()) { handler.SignalError(IostatEndfileUnwritable, "ENDFILE(UNIT=%d) on read-only file", unitNumber()); @@ -600,9 +637,11 @@ // ENDFILE after ENDFILE } else { DoEndfile(handler); - // Explicit ENDFILE leaves position *after* the endfile record - RUNTIME_CHECK(handler, endfileRecordNumber.has_value()); - currentRecordNumber = *endfileRecordNumber + 1; + if (access == Access::Sequential) { + // Explicit ENDFILE leaves position *after* the endfile record + RUNTIME_CHECK(handler, endfileRecordNumber.has_value()); + currentRecordNumber = *endfileRecordNumber + 1; + } } } @@ -611,12 +650,18 @@ handler.SignalError(IostatRewindNonSequential, "REWIND(UNIT=%d) on non-sequential file", unitNumber()); } else { - DoImpliedEndfile(handler); - SetPosition(0); + SetPosition(0, handler); currentRecordNumber = 1; } } +void ExternalFileUnit::SetPosition(std::int64_t pos, IoErrorHandler &handler) { + DoImpliedEndfile(handler); + frameOffsetInFile_ = pos; + recordOffsetInFrame_ = 0; + BeginRecord(); +} + void ExternalFileUnit::EndIoStatement() { io_.reset(); u_.emplace(); @@ -665,7 +710,7 @@ positionInRecord = sizeof header; } -void ExternalFileUnit::BeginSequentialVariableFormattedInputRecord( +void ExternalFileUnit::BeginVariableFormattedInputRecord( IoErrorHandler &handler) { if (this == defaultInput) { if (defaultOutput) { @@ -690,7 +735,7 @@ } break; } - } while (!SetSequentialVariableFormattedRecordLength()); + } while (!SetVariableFormattedRecordLength()); } void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) { @@ -783,14 +828,17 @@ void ExternalFileUnit::DoImpliedEndfile(IoErrorHandler &handler) { if (impliedEndfile_) { impliedEndfile_ = false; - if (access == Access::Sequential && mayPosition()) { + if (access != Access::Direct && IsRecordFile() && mayPosition()) { DoEndfile(handler); } } } void ExternalFileUnit::DoEndfile(IoErrorHandler &handler) { - endfileRecordNumber = currentRecordNumber; + if (access == Access::Sequential) { + endfileRecordNumber = currentRecordNumber; + } + FlushOutput(handler); Truncate(frameOffsetInFile_ + recordOffsetInFrame_, handler); BeginRecord(); impliedEndfile_ = false; diff --git a/flang/unittests/Runtime/ExternalIOTest.cpp b/flang/unittests/Runtime/ExternalIOTest.cpp --- a/flang/unittests/Runtime/ExternalIOTest.cpp +++ b/flang/unittests/Runtime/ExternalIOTest.cpp @@ -626,7 +626,7 @@ ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) << "EndIoStatement() for OutputAscii"; - // Verify that the output was written in the record read in non avdancing + // Verify that the output was written in the record read in non advancing // mode, after the read part, and that the end was truncated. // REWIND(UNIT=unit) @@ -647,7 +647,7 @@ << "EndIoStatement() for Read "; ASSERT_EQ(resultRecord, expectedRecord) - << "Record after non advancing read followed by wrote"; + << "Record after non advancing read followed by write"; } TEST(ExternalIOTests, TestWriteAfterEndfile) {