forked from OSchip/llvm-project
[flang] Debugging of ACCESS='STREAM' I/O (take 2)
Corrects the runtime implementation of I/O on files with the access mode ACCESS='STREAM'. This is a collection of edge-case tweaks to ensure that the distinctions between stream and direct/sequential files, unformatted or formatted, are respected where appropriate. Moves NextInField() from io-stmt.h to io-stmt.cpp -- it was getting too big to keep in a header. This patch exposed a problem with the I/O runtime on Windows and it was reverted. This version also fixes that problem; files are now opened on Windows in binary mode to prevent inadvertent insertions of carriage returns before line feeds, and those line endings (CR+LF) are now explicitly generated. Differential Revision: https://reviews.llvm.org/D119015
This commit is contained in:
parent
eddf384965
commit
991696c2eb
|
@ -45,7 +45,7 @@ enum Iostat {
|
|||
IostatInternalWriteOverrun,
|
||||
IostatErrorInFormat,
|
||||
IostatErrorInKeyword,
|
||||
IostatEndfileNonSequential,
|
||||
IostatEndfileDirect,
|
||||
IostatEndfileUnwritable,
|
||||
IostatOpenBadRecl,
|
||||
IostatOpenUnknownSize,
|
||||
|
|
|
@ -22,8 +22,6 @@ class IoStatementState;
|
|||
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 @@ struct ConnectionAttributes {
|
|||
std::optional<bool> isUnformatted; // FORM='UNFORMATTED' if true
|
||||
bool isUTF8{false}; // ENCODING='UTF-8'
|
||||
std::optional<std::int64_t> 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 {
|
||||
|
|
|
@ -19,7 +19,7 @@ static bool EditBOZInput(IoStatementState &io, const DataEdit &edit, void *n,
|
|||
std::optional<int> remaining;
|
||||
std::optional<char32_t> 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 @@ static bool ScanNumericPrefix(IoStatementState &io, const DataEdit &edit,
|
|||
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 EditIntegerInput(
|
|||
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 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
|
|||
// 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 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
|
|||
}
|
||||
if (next && *next == '(') { // NaN(...)
|
||||
while (next && *next != ')') {
|
||||
next = io.NextInField(remaining);
|
||||
next = io.NextInField(remaining, edit);
|
||||
}
|
||||
}
|
||||
exponent = 0;
|
||||
|
@ -185,7 +185,7 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
|
|||
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 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
|
|||
// 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 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
|
|||
(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 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
|
|||
// 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 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
|
|||
}
|
||||
} 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 @@ bool EditLogicalInput(IoStatementState &io, const DataEdit &edit, bool &x) {
|
|||
std::optional<int> remaining;
|
||||
std::optional<char32_t> 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 @@ bool EditLogicalInput(IoStatementState &io, const DataEdit &edit, bool &x) {
|
|||
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 EditDelimitedCharacterInput(
|
|||
}
|
||||
|
||||
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 @@ static bool EditListDirectedDefaultCharacterInput(
|
|||
// Undelimited list-directed character input: stop at a value separator
|
||||
// or the end of the current record.
|
||||
std::optional<int> remaining{length};
|
||||
for (std::optional<char32_t> next{io.NextInField(remaining)}; next;
|
||||
next = io.NextInField(remaining)) {
|
||||
while (std::optional<char32_t> next{io.NextInField(remaining, edit)}) {
|
||||
switch (*next) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
|
@ -555,7 +554,7 @@ bool EditDefaultCharacterInput(
|
|||
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 @@ bool EditDefaultCharacterInput(
|
|||
// characters. When the variable is wider than the field, there's
|
||||
// trailing padding.
|
||||
std::int64_t skip{*remaining - static_cast<std::int64_t>(length)};
|
||||
for (std::optional<char32_t> next{io.NextInField(remaining)}; next;
|
||||
next = io.NextInField(remaining)) {
|
||||
while (std::optional<char32_t> next{io.NextInField(remaining, edit)}) {
|
||||
if (skip > 0) {
|
||||
--skip;
|
||||
io.GotChar(-1);
|
||||
|
|
|
@ -45,8 +45,8 @@ static int openfile_mkstemp(IoErrorHandler &handler) {
|
|||
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 @@ void OpenFile::Open(OpenStatus status, std::optional<Action> action,
|
|||
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 @@ void OpenFile::Predefine(int fd) {
|
|||
mayRead_ = fd == 0;
|
||||
mayWrite_ = fd != 0;
|
||||
mayPosition_ = false;
|
||||
#ifdef _WIN32
|
||||
isWindowsTextFile_ = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenFile::Close(CloseStatus status, IoErrorHandler &handler) {
|
||||
|
|
|
@ -35,8 +35,8 @@ public:
|
|||
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<FileOffset> knownSize() const { return knownSize_; }
|
||||
|
||||
bool IsConnected() const { return fd_ >= 0; }
|
||||
|
@ -98,6 +98,7 @@ private:
|
|||
FileOffset position_{0};
|
||||
std::optional<FileOffset> knownSize_;
|
||||
bool isTerminal_{false};
|
||||
bool isWindowsTextFile_{false}; // expands LF to CR+LF on write
|
||||
|
||||
int nextId_;
|
||||
OwningPtr<Pending> pending_;
|
||||
|
|
|
@ -524,18 +524,17 @@ bool IONAME(SetPad)(Cookie cookie, const char *keyword, std::size_t length) {
|
|||
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<std::intmax_t>(pos));
|
||||
if (pos < 1) { // POS=1 is beginning of file (12.6.2.11)
|
||||
handler.SignalError("POS=%zd is invalid", static_cast<std::intmax_t>(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(SetPos)(Cookie cookie, std::int64_t pos) {
|
|||
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<std::intmax_t>(rec));
|
||||
handler.SignalError("REC=%zd is invalid", static_cast<std::intmax_t>(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;
|
||||
}
|
||||
|
|
|
@ -268,7 +268,7 @@ ExternalIoStatementState<DIR>::ExternalIoStatementState(
|
|||
: 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 @@ bool IoStatementState::EmitField(
|
|||
}
|
||||
}
|
||||
|
||||
std::optional<char32_t> IoStatementState::NextInField(
|
||||
std::optional<int> &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<char32_t>{' '};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool IoStatementState::Inquire(
|
||||
InquiryKeywordHash inquiry, char *out, std::size_t chars) {
|
||||
return std::visit(
|
||||
|
@ -1060,7 +1120,7 @@ bool InquireUnitState::Inquire(
|
|||
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()) {
|
||||
|
|
|
@ -142,7 +142,7 @@ public:
|
|||
}
|
||||
SkipSpaces(remaining);
|
||||
}
|
||||
return NextInField(remaining);
|
||||
return NextInField(remaining, edit);
|
||||
}
|
||||
|
||||
std::optional<char32_t> SkipSpaces(std::optional<int> &remaining) {
|
||||
|
@ -163,59 +163,10 @@ public:
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Acquires the next input character, respecting any applicable field width
|
||||
// or separator character.
|
||||
std::optional<char32_t> NextInField(
|
||||
std::optional<int> &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<char32_t>{' '};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<int> &remaining, const DataEdit &);
|
||||
|
||||
// Skips spaces, advances records, and ignores NAMELIST comments
|
||||
std::optional<char32_t> GetNextNonBlank() {
|
||||
|
|
|
@ -33,8 +33,8 @@ const char *IostatErrorString(int iostat) {
|
|||
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:
|
||||
|
|
|
@ -151,7 +151,7 @@ void ExternalFileUnit::OpenUnit(std::optional<OpenStatus> status,
|
|||
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<std::int64_t>::max() - 2;
|
||||
|
@ -288,7 +288,12 @@ bool ExternalFileUnit::Emit(const char *data, std::size_t bytes,
|
|||
header = static_cast<int>(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 @@ bool ExternalFileUnit::Receive(char *data, std::size_t bytes,
|
|||
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 @@ bool ExternalFileUnit::Receive(char *data, std::size_t bytes,
|
|||
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<char32_t> ExternalFileUnit::GetCurrentChar(
|
||||
|
@ -383,18 +397,20 @@ const char *ExternalFileUnit::FrameNextInput(
|
|||
auto at{recordOffsetInFrame_ + positionInRecord};
|
||||
auto need{static_cast<std::size_t>(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 @@ bool ExternalFileUnit::BeginReadingRecord(IoErrorHandler &handler) {
|
|||
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 @@ void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) {
|
|||
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 @@ void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) {
|
|||
Frame()[recordOffsetInFrame_] == '\r') {
|
||||
++recordOffsetInFrame_;
|
||||
}
|
||||
if (FrameLength() >= recordOffsetInFrame_ &&
|
||||
if (FrameLength() > recordOffsetInFrame_ &&
|
||||
Frame()[recordOffsetInFrame_] == '\n') {
|
||||
++recordOffsetInFrame_;
|
||||
}
|
||||
|
@ -482,8 +501,12 @@ void ExternalFileUnit::FinishReadingRecord(IoErrorHandler &handler) {
|
|||
}
|
||||
}
|
||||
}
|
||||
++currentRecordNumber;
|
||||
} else { // unformatted stream
|
||||
furthestPositionInRecord =
|
||||
std::max(furthestPositionInRecord, positionInRecord);
|
||||
frameOffsetInFile_ += recordOffsetInFrame_ + furthestPositionInRecord;
|
||||
}
|
||||
++currentRecordNumber;
|
||||
BeginRecord();
|
||||
}
|
||||
|
||||
|
@ -494,8 +517,10 @@ bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {
|
|||
} 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 @@ bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {
|
|||
*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 @@ bool ExternalFileUnit::AdvanceRecord(IoErrorHandler &handler) {
|
|||
Emit(reinterpret_cast<const char *>(&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::FlushIfTerminal(IoErrorHandler &handler) {
|
|||
}
|
||||
|
||||
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 @@ void ExternalFileUnit::Endfile(IoErrorHandler &handler) {
|
|||
// 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 @@ void ExternalFileUnit::Rewind(IoErrorHandler &handler) {
|
|||
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<std::monostate>();
|
||||
|
@ -665,7 +710,7 @@ void ExternalFileUnit::BeginSequentialVariableUnformattedInputRecord(
|
|||
positionInRecord = sizeof header;
|
||||
}
|
||||
|
||||
void ExternalFileUnit::BeginSequentialVariableFormattedInputRecord(
|
||||
void ExternalFileUnit::BeginVariableFormattedInputRecord(
|
||||
IoErrorHandler &handler) {
|
||||
if (this == defaultInput) {
|
||||
if (defaultOutput) {
|
||||
|
@ -690,7 +735,7 @@ void ExternalFileUnit::BeginSequentialVariableFormattedInputRecord(
|
|||
}
|
||||
break;
|
||||
}
|
||||
} while (!SetSequentialVariableFormattedRecordLength());
|
||||
} while (!SetVariableFormattedRecordLength());
|
||||
}
|
||||
|
||||
void ExternalFileUnit::BackspaceFixedRecord(IoErrorHandler &handler) {
|
||||
|
@ -783,14 +828,17 @@ void ExternalFileUnit::BackspaceVariableFormattedRecord(
|
|||
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;
|
||||
|
|
|
@ -90,10 +90,10 @@ public:
|
|||
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 @@ private:
|
|||
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_;
|
||||
|
|
|
@ -626,7 +626,7 @@ TEST(ExternalIOTests, TestWriteAfterNonAvancingInput) {
|
|||
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 @@ TEST(ExternalIOTests, TestWriteAfterNonAvancingInput) {
|
|||
<< "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) {
|
||||
|
|
Loading…
Reference in New Issue