//===-- runtime/io-stmt.cpp -----------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "io-stmt.h" #include "connection.h" #include "emit-encoded.h" #include "format.h" #include "tools.h" #include "unit.h" #include "utf.h" #include "flang/Runtime/memory.h" #include #include #include #include #include namespace Fortran::runtime::io { bool IoStatementBase::Emit(const char *, std::size_t, std::size_t) { return false; } std::size_t IoStatementBase::GetNextInputBytes(const char *&p) { p = nullptr; return 0; } bool IoStatementBase::AdvanceRecord(int) { return false; } void IoStatementBase::BackspaceRecord() {} bool IoStatementBase::Receive(char *, std::size_t, std::size_t) { return false; } std::optional IoStatementBase::GetNextDataEdit( IoStatementState &, int) { return std::nullopt; } ExternalFileUnit *IoStatementBase::GetExternalFileUnit() const { return nullptr; } bool IoStatementBase::BeginReadingRecord() { return true; } void IoStatementBase::FinishReadingRecord() {} void IoStatementBase::HandleAbsolutePosition(std::int64_t) {} void IoStatementBase::HandleRelativePosition(std::int64_t) {} bool IoStatementBase::Inquire(InquiryKeywordHash, char *, std::size_t) { return false; } bool IoStatementBase::Inquire(InquiryKeywordHash, bool &) { return false; } bool IoStatementBase::Inquire(InquiryKeywordHash, std::int64_t, bool &) { return false; } bool IoStatementBase::Inquire(InquiryKeywordHash, std::int64_t &) { return false; } std::int64_t IoStatementBase::InquirePos() { return 0; } void IoStatementBase::BadInquiryKeywordHashCrash(InquiryKeywordHash inquiry) { char buffer[16]; const char *decode{InquiryKeywordHashDecode(buffer, sizeof buffer, inquiry)}; Crash("Bad InquiryKeywordHash 0x%x (%s)", inquiry, decode ? decode : "(cannot decode)"); } template InternalIoStatementState::InternalIoStatementState( Buffer scalar, std::size_t length, const char *sourceFile, int sourceLine) : IoStatementBase{sourceFile, sourceLine}, unit_{scalar, length, 1} {} template InternalIoStatementState::InternalIoStatementState( const Descriptor &d, const char *sourceFile, int sourceLine) : IoStatementBase{sourceFile, sourceLine}, unit_{d, *this} {} template bool InternalIoStatementState::Emit( const char *data, std::size_t bytes, std::size_t /*elementBytes*/) { if constexpr (DIR == Direction::Input) { Crash("InternalIoStatementState::Emit() called"); return false; } return unit_.Emit(data, bytes, *this); } template std::size_t InternalIoStatementState::GetNextInputBytes(const char *&p) { return unit_.GetNextInputBytes(p, *this); } template bool InternalIoStatementState::AdvanceRecord(int n) { while (n-- > 0) { if (!unit_.AdvanceRecord(*this)) { return false; } } return true; } template void InternalIoStatementState::BackspaceRecord() { unit_.BackspaceRecord(*this); } template int InternalIoStatementState::EndIoStatement() { if constexpr (DIR == Direction::Output) { unit_.EndIoStatement(); // fill } auto result{IoStatementBase::EndIoStatement()}; if (free_) { FreeMemory(this); } return result; } template void InternalIoStatementState::HandleAbsolutePosition(std::int64_t n) { return unit_.HandleAbsolutePosition(n); } template void InternalIoStatementState::HandleRelativePosition(std::int64_t n) { return unit_.HandleRelativePosition(n); } template std::int64_t InternalIoStatementState::InquirePos() { return unit_.InquirePos(); } template InternalFormattedIoStatementState::InternalFormattedIoStatementState( Buffer buffer, std::size_t length, const CharType *format, std::size_t formatLength, const Descriptor *formatDescriptor, const char *sourceFile, int sourceLine) : InternalIoStatementState{buffer, length, sourceFile, sourceLine}, ioStatementState_{*this}, format_{*this, format, formatLength, formatDescriptor} {} template InternalFormattedIoStatementState::InternalFormattedIoStatementState( const Descriptor &d, const CharType *format, std::size_t formatLength, const Descriptor *formatDescriptor, const char *sourceFile, int sourceLine) : InternalIoStatementState{d, sourceFile, sourceLine}, ioStatementState_{*this}, format_{*this, format, formatLength, formatDescriptor} {} template void InternalFormattedIoStatementState::CompleteOperation() { if (!this->completedOperation()) { if constexpr (DIR == Direction::Output) { format_.Finish(*this); // ignore any remaining input positioning actions } IoStatementBase::CompleteOperation(); } } template int InternalFormattedIoStatementState::EndIoStatement() { CompleteOperation(); return InternalIoStatementState::EndIoStatement(); } template InternalListIoStatementState::InternalListIoStatementState( Buffer buffer, std::size_t length, const char *sourceFile, int sourceLine) : InternalIoStatementState{buffer, length, sourceFile, sourceLine}, ioStatementState_{*this} {} template InternalListIoStatementState::InternalListIoStatementState( const Descriptor &d, const char *sourceFile, int sourceLine) : InternalIoStatementState{d, sourceFile, sourceLine}, ioStatementState_{*this} {} ExternalIoStatementBase::ExternalIoStatementBase( ExternalFileUnit &unit, const char *sourceFile, int sourceLine) : IoStatementBase{sourceFile, sourceLine}, unit_{unit} {} MutableModes &ExternalIoStatementBase::mutableModes() { if (const ChildIo * child{unit_.GetChildIo()}) { return child->parent().mutableModes(); } return unit_.modes; } ConnectionState &ExternalIoStatementBase::GetConnectionState() { return unit_; } int ExternalIoStatementBase::EndIoStatement() { CompleteOperation(); auto result{IoStatementBase::EndIoStatement()}; unit_.EndIoStatement(); // annihilates *this in unit_.u_ return result; } void ExternalIoStatementBase::SetAsynchronous() { asynchronousID_ = unit().GetAsynchronousId(*this); } std::int64_t ExternalIoStatementBase::InquirePos() { return unit_.InquirePos(); } void OpenStatementState::set_path(const char *path, std::size_t length) { pathLength_ = TrimTrailingSpaces(path, length); path_ = SaveDefaultCharacter(path, pathLength_, *this); } void OpenStatementState::CompleteOperation() { if (completedOperation()) { return; } if (position_) { if (access_ && *access_ == Access::Direct) { SignalError("POSITION= may not be set with ACCESS='DIRECT'"); position_.reset(); } } if (status_) { // 12.5.6.10 if ((*status_ == OpenStatus::New || *status_ == OpenStatus::Replace) && !path_.get()) { SignalError("FILE= required on OPEN with STATUS='NEW' or 'REPLACE'"); } else if (*status_ == OpenStatus::Scratch && path_.get()) { SignalError("FILE= may not appear on OPEN with STATUS='SCRATCH'"); } } if (path_.get() || wasExtant_ || (status_ && *status_ == OpenStatus::Scratch)) { unit().OpenUnit(status_, action_, position_.value_or(Position::AsIs), std::move(path_), pathLength_, convert_, *this); } else { unit().OpenAnonymousUnit( status_, action_, position_.value_or(Position::AsIs), convert_, *this); } if (access_) { if (*access_ != unit().access) { if (wasExtant_) { SignalError("ACCESS= may not be changed on an open unit"); access_.reset(); } } if (access_) { unit().access = *access_; } } if (!unit().isUnformatted) { unit().isUnformatted = isUnformatted_; } if (isUnformatted_ && *isUnformatted_ != *unit().isUnformatted) { if (wasExtant_) { SignalError("FORM= may not be changed on an open unit"); } unit().isUnformatted = *isUnformatted_; } if (!unit().isUnformatted) { // Set default format (C.7.4 point 2). unit().isUnformatted = unit().access != Access::Sequential; } if (!wasExtant_ && InError()) { // Release the new unit on failure unit().CloseUnit(CloseStatus::Delete, *this); unit().DestroyClosed(); } IoStatementBase::CompleteOperation(); } int OpenStatementState::EndIoStatement() { CompleteOperation(); return ExternalIoStatementBase::EndIoStatement(); } int CloseStatementState::EndIoStatement() { CompleteOperation(); int result{ExternalIoStatementBase::EndIoStatement()}; unit().CloseUnit(status_, *this); unit().DestroyClosed(); return result; } void NoUnitIoStatementState::CompleteOperation() { SignalPendingError(); IoStatementBase::CompleteOperation(); } int NoUnitIoStatementState::EndIoStatement() { CompleteOperation(); auto result{IoStatementBase::EndIoStatement()}; FreeMemory(this); return result; } template ExternalIoStatementState::ExternalIoStatementState( ExternalFileUnit &unit, const char *sourceFile, int sourceLine) : 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 // 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. unit.furthestPositionInRecord = std::max(unit.furthestPositionInRecord, unit.positionInRecord); } } template void ExternalIoStatementState::CompleteOperation() { if (completedOperation()) { return; } if constexpr (DIR == Direction::Input) { BeginReadingRecord(); // in case there were no I/O items if (mutableModes().nonAdvancing && !InError()) { unit().leftTabLimit = unit().furthestPositionInRecord; } else { FinishReadingRecord(); } } else { // output if (mutableModes().nonAdvancing) { // Make effects of positioning past the last Emit() visible with blanks. std::int64_t n{unit().positionInRecord - unit().furthestPositionInRecord}; while (n-- > 0 && unit().Emit(" ", 1, 1, *this)) { } unit().leftTabLimit = unit().positionInRecord; } else { unit().AdvanceRecord(*this); } unit().FlushIfTerminal(*this); } return IoStatementBase::CompleteOperation(); } template int ExternalIoStatementState::EndIoStatement() { CompleteOperation(); return ExternalIoStatementBase::EndIoStatement(); } template bool ExternalIoStatementState::Emit( const char *data, std::size_t bytes, std::size_t elementBytes) { if constexpr (DIR == Direction::Input) { Crash("ExternalIoStatementState::Emit(char) called for input statement"); } return unit().Emit(data, bytes, elementBytes, *this); } template std::size_t ExternalIoStatementState::GetNextInputBytes(const char *&p) { return unit().GetNextInputBytes(p, *this); } template bool ExternalIoStatementState::AdvanceRecord(int n) { while (n-- > 0) { if (!unit().AdvanceRecord(*this)) { return false; } } return true; } template void ExternalIoStatementState::BackspaceRecord() { unit().BackspaceRecord(*this); } template void ExternalIoStatementState::HandleAbsolutePosition(std::int64_t n) { return unit().HandleAbsolutePosition(n); } template void ExternalIoStatementState::HandleRelativePosition(std::int64_t n) { return unit().HandleRelativePosition(n); } template bool ExternalIoStatementState::BeginReadingRecord() { if constexpr (DIR == Direction::Input) { return unit().BeginReadingRecord(*this); } else { Crash("ExternalIoStatementState::BeginReadingRecord() " "called"); return false; } } template void ExternalIoStatementState::FinishReadingRecord() { if constexpr (DIR == Direction::Input) { unit().FinishReadingRecord(*this); } else { Crash("ExternalIoStatementState::FinishReadingRecord() " "called"); } } template ExternalFormattedIoStatementState::ExternalFormattedIoStatementState( ExternalFileUnit &unit, const CHAR *format, std::size_t formatLength, const Descriptor *formatDescriptor, const char *sourceFile, int sourceLine) : ExternalIoStatementState{unit, sourceFile, sourceLine}, format_{*this, format, formatLength, formatDescriptor} {} template void ExternalFormattedIoStatementState::CompleteOperation() { if (this->completedOperation()) { return; } if constexpr (DIR == Direction::Input) { this->BeginReadingRecord(); // in case there were no I/O items } format_.Finish(*this); return ExternalIoStatementState::CompleteOperation(); } template int ExternalFormattedIoStatementState::EndIoStatement() { CompleteOperation(); return ExternalIoStatementState::EndIoStatement(); } std::optional IoStatementState::GetNextDataEdit(int n) { return common::visit( [&](auto &x) { return x.get().GetNextDataEdit(*this, n); }, u_); } bool IoStatementState::Emit( const char *data, std::size_t bytes, std::size_t elementBytes) { return common::visit( [=](auto &x) { return x.get().Emit(data, bytes, elementBytes); }, u_); } bool IoStatementState::Receive( char *data, std::size_t n, std::size_t elementBytes) { return common::visit( [=](auto &x) { return x.get().Receive(data, n, elementBytes); }, u_); } std::size_t IoStatementState::GetNextInputBytes(const char *&p) { return common::visit( [&](auto &x) { return x.get().GetNextInputBytes(p); }, u_); } bool IoStatementState::AdvanceRecord(int n) { return common::visit([=](auto &x) { return x.get().AdvanceRecord(n); }, u_); } void IoStatementState::BackspaceRecord() { common::visit([](auto &x) { x.get().BackspaceRecord(); }, u_); } void IoStatementState::HandleRelativePosition(std::int64_t n) { common::visit([=](auto &x) { x.get().HandleRelativePosition(n); }, u_); } void IoStatementState::HandleAbsolutePosition(std::int64_t n) { common::visit([=](auto &x) { x.get().HandleAbsolutePosition(n); }, u_); } void IoStatementState::CompleteOperation() { common::visit([](auto &x) { x.get().CompleteOperation(); }, u_); } int IoStatementState::EndIoStatement() { return common::visit([](auto &x) { return x.get().EndIoStatement(); }, u_); } ConnectionState &IoStatementState::GetConnectionState() { return common::visit( [](auto &x) -> ConnectionState & { return x.get().GetConnectionState(); }, u_); } MutableModes &IoStatementState::mutableModes() { return common::visit( [](auto &x) -> MutableModes & { return x.get().mutableModes(); }, u_); } bool IoStatementState::BeginReadingRecord() { return common::visit( [](auto &x) { return x.get().BeginReadingRecord(); }, u_); } IoErrorHandler &IoStatementState::GetIoErrorHandler() const { return common::visit( [](auto &x) -> IoErrorHandler & { return static_cast(x.get()); }, u_); } ExternalFileUnit *IoStatementState::GetExternalFileUnit() const { return common::visit( [](auto &x) { return x.get().GetExternalFileUnit(); }, u_); } std::optional IoStatementState::GetCurrentChar( std::size_t &byteCount) { const char *p{nullptr}; std::size_t bytes{GetNextInputBytes(p)}; if (bytes == 0) { byteCount = 0; return std::nullopt; } else { const ConnectionState &connection{GetConnectionState()}; if (connection.isUTF8) { std::size_t length{MeasureUTF8Bytes(*p)}; if (length <= bytes) { if (auto result{DecodeUTF8(p)}) { byteCount = length; return result; } } GetIoErrorHandler().SignalError(IostatUTF8Decoding); // Error recovery: return the next byte } else if (connection.internalIoCharKind > 1) { byteCount = connection.internalIoCharKind; if (byteCount == 2) { return *reinterpret_cast(p); } else { return *reinterpret_cast(p); } } byteCount = 1; return *p; } } std::optional IoStatementState::NextInField( std::optional &remaining, const DataEdit &edit) { std::size_t byteCount{0}; if (!remaining) { // Stream, list-directed, or NAMELIST if (auto next{GetCurrentChar(byteCount)}) { if (edit.IsListDirected()) { // list-directed or NAMELIST: check for separators switch (*next) { case ' ': case '\t': case '/': case '(': case ')': case '\'': case '"': case '*': case '\n': // for stream access return std::nullopt; case ',': if (!(edit.modes.editingFlags & decimalComma)) { return std::nullopt; } break; case ';': if (edit.modes.editingFlags & decimalComma) { return std::nullopt; } break; default: break; } } HandleRelativePosition(byteCount); GotChar(byteCount); return next; } } else if (*remaining > 0) { if (auto next{GetCurrentChar(byteCount)}) { if (byteCount > static_cast(*remaining)) { return std::nullopt; } *remaining -= byteCount; HandleRelativePosition(byteCount); GotChar(byteCount); return next; } if (CheckForEndOfRecord()) { // do padding --*remaining; return std::optional{' '}; } } return std::nullopt; } bool IoStatementState::CheckForEndOfRecord() { const ConnectionState &connection{GetConnectionState()}; if (!connection.IsAtEOF()) { if (auto length{connection.EffectiveRecordLength()}) { if (connection.positionInRecord >= *length) { IoErrorHandler &handler{GetIoErrorHandler()}; const auto &modes{mutableModes()}; if (modes.nonAdvancing) { if (connection.access == Access::Stream && connection.unterminatedRecord) { // Reading final unterminated record left by a // non-advancing WRITE on a stream file prior to // positioning or ENDFILE. handler.SignalEnd(); } else { handler.SignalEor(); } } else if (!modes.pad) { handler.SignalError(IostatRecordReadOverrun); } return modes.pad; // PAD='YES' } } } return false; } bool IoStatementState::Inquire( InquiryKeywordHash inquiry, char *out, std::size_t chars) { return common::visit( [&](auto &x) { return x.get().Inquire(inquiry, out, chars); }, u_); } bool IoStatementState::Inquire(InquiryKeywordHash inquiry, bool &out) { return common::visit( [&](auto &x) { return x.get().Inquire(inquiry, out); }, u_); } bool IoStatementState::Inquire( InquiryKeywordHash inquiry, std::int64_t id, bool &out) { return common::visit( [&](auto &x) { return x.get().Inquire(inquiry, id, out); }, u_); } bool IoStatementState::Inquire(InquiryKeywordHash inquiry, std::int64_t &n) { return common::visit( [&](auto &x) { return x.get().Inquire(inquiry, n); }, u_); } std::int64_t IoStatementState::InquirePos() { return common::visit([&](auto &x) { return x.get().InquirePos(); }, u_); } void IoStatementState::GotChar(int n) { if (auto *formattedIn{ get_if>()}) { formattedIn->GotChar(n); } else { GetIoErrorHandler().Crash("IoStatementState::GotChar() called for " "statement that is not formatted input"); } } std::size_t FormattedIoStatementState::GetEditDescriptorChars() const { return chars_; } void FormattedIoStatementState::GotChar(int n) { chars_ += n; } bool ListDirectedStatementState::EmitLeadingSpaceOrAdvance( IoStatementState &io, std::size_t length, bool isCharacter) { if (length == 0) { return true; } const ConnectionState &connection{io.GetConnectionState()}; int space{connection.positionInRecord == 0 || !(isCharacter && lastWasUndelimitedCharacter())}; set_lastWasUndelimitedCharacter(false); if (connection.NeedAdvance(space + length)) { return io.AdvanceRecord(); } if (space) { return EmitAscii(io, " ", 1); } return true; } std::optional ListDirectedStatementState::GetNextDataEdit( IoStatementState &io, int maxRepeat) { DataEdit edit; edit.descriptor = DataEdit::ListDirected; edit.repeat = maxRepeat; edit.modes = io.mutableModes(); return edit; } std::optional ListDirectedStatementState::GetNextDataEdit( IoStatementState &io, int maxRepeat) { // N.B. list-directed transfers cannot be nonadvancing (C1221) ConnectionState &connection{io.GetConnectionState()}; DataEdit edit; edit.descriptor = DataEdit::ListDirected; edit.repeat = 1; // may be overridden below edit.modes = io.mutableModes(); if (hitSlash_) { // everything after '/' is nullified edit.descriptor = DataEdit::ListDirectedNullValue; return edit; } char32_t comma{','}; if (edit.modes.editingFlags & decimalComma) { comma = ';'; } std::size_t byteCount{0}; if (remaining_ > 0 && !realPart_) { // "r*c" repetition in progress RUNTIME_CHECK(io.GetIoErrorHandler(), repeatPosition_.has_value()); repeatPosition_.reset(); // restores the saved position if (!imaginaryPart_) { edit.repeat = std::min(remaining_, maxRepeat); auto ch{io.GetCurrentChar(byteCount)}; if (!ch || *ch == ' ' || *ch == '\t' || *ch == comma) { // "r*" repeated null edit.descriptor = DataEdit::ListDirectedNullValue; } } remaining_ -= edit.repeat; if (remaining_ > 0) { repeatPosition_.emplace(io); } if (!imaginaryPart_) { return edit; } } // Skip separators, handle a "r*c" repeat count; see 13.10.2 in Fortran 2018 if (imaginaryPart_) { imaginaryPart_ = false; } else if (realPart_) { realPart_ = false; imaginaryPart_ = true; edit.descriptor = DataEdit::ListDirectedImaginaryPart; } auto ch{io.GetNextNonBlank(byteCount)}; if (ch && *ch == comma && eatComma_) { // Consume comma & whitespace after previous item. // This includes the comma between real and imaginary components // in list-directed/NAMELIST complex input. // (When DECIMAL='COMMA', the comma is actually a semicolon.) io.HandleRelativePosition(byteCount); ch = io.GetNextNonBlank(byteCount); } eatComma_ = true; if (!ch) { return std::nullopt; } if (*ch == '/') { hitSlash_ = true; edit.descriptor = DataEdit::ListDirectedNullValue; return edit; } if (*ch == comma) { // separator: null value edit.descriptor = DataEdit::ListDirectedNullValue; return edit; } if (imaginaryPart_) { // can't repeat components return edit; } if (*ch >= '0' && *ch <= '9') { // look for "r*" repetition count auto start{connection.positionInRecord}; int r{0}; do { static auto constexpr clamp{(std::numeric_limits::max() - '9') / 10}; if (r >= clamp) { r = 0; break; } r = 10 * r + (*ch - '0'); io.HandleRelativePosition(byteCount); ch = io.GetCurrentChar(byteCount); } while (ch && *ch >= '0' && *ch <= '9'); if (r > 0 && ch && *ch == '*') { // subtle: r must be nonzero io.HandleRelativePosition(byteCount); ch = io.GetCurrentChar(byteCount); if (ch && *ch == '/') { // r*/ hitSlash_ = true; edit.descriptor = DataEdit::ListDirectedNullValue; return edit; } if (!ch || *ch == ' ' || *ch == '\t' || *ch == comma) { // "r*" null edit.descriptor = DataEdit::ListDirectedNullValue; } edit.repeat = std::min(r, maxRepeat); remaining_ = r - edit.repeat; if (remaining_ > 0) { repeatPosition_.emplace(io); } } else { // not a repetition count, just an integer value; rewind connection.positionInRecord = start; } } if (!imaginaryPart_ && ch && *ch == '(') { realPart_ = true; io.HandleRelativePosition(byteCount); edit.descriptor = DataEdit::ListDirectedRealPart; } return edit; } template bool ExternalUnformattedIoStatementState::Receive( char *data, std::size_t bytes, std::size_t elementBytes) { if constexpr (DIR == Direction::Output) { this->Crash("ExternalUnformattedIoStatementState::Receive() called for " "output statement"); } return this->unit().Receive(data, bytes, elementBytes, *this); } template ChildIoStatementState::ChildIoStatementState( ChildIo &child, const char *sourceFile, int sourceLine) : IoStatementBase{sourceFile, sourceLine}, child_{child} {} template MutableModes &ChildIoStatementState::mutableModes() { return child_.parent().mutableModes(); } template ConnectionState &ChildIoStatementState::GetConnectionState() { return child_.parent().GetConnectionState(); } template ExternalFileUnit *ChildIoStatementState::GetExternalFileUnit() const { return child_.parent().GetExternalFileUnit(); } template int ChildIoStatementState::EndIoStatement() { CompleteOperation(); auto result{IoStatementBase::EndIoStatement()}; child_.EndIoStatement(); // annihilates *this in child_.u_ return result; } template bool ChildIoStatementState::Emit( const char *data, std::size_t bytes, std::size_t elementBytes) { return child_.parent().Emit(data, bytes, elementBytes); } template std::size_t ChildIoStatementState::GetNextInputBytes(const char *&p) { return child_.parent().GetNextInputBytes(p); } template void ChildIoStatementState::HandleAbsolutePosition(std::int64_t n) { return child_.parent().HandleAbsolutePosition(n); } template void ChildIoStatementState::HandleRelativePosition(std::int64_t n) { return child_.parent().HandleRelativePosition(n); } template ChildFormattedIoStatementState::ChildFormattedIoStatementState( ChildIo &child, const CHAR *format, std::size_t formatLength, const Descriptor *formatDescriptor, const char *sourceFile, int sourceLine) : ChildIoStatementState{child, sourceFile, sourceLine}, mutableModes_{child.parent().mutableModes()}, format_{*this, format, formatLength, formatDescriptor} {} template void ChildFormattedIoStatementState::CompleteOperation() { if (!this->completedOperation()) { format_.Finish(*this); ChildIoStatementState::CompleteOperation(); } } template int ChildFormattedIoStatementState::EndIoStatement() { CompleteOperation(); return ChildIoStatementState::EndIoStatement(); } template bool ChildFormattedIoStatementState::AdvanceRecord(int n) { return this->child().parent().AdvanceRecord(n); } template bool ChildUnformattedIoStatementState::Receive( char *data, std::size_t bytes, std::size_t elementBytes) { return this->child().parent().Receive(data, bytes, elementBytes); } template class InternalIoStatementState; template class InternalIoStatementState; template class InternalFormattedIoStatementState; template class InternalFormattedIoStatementState; template class InternalListIoStatementState; template class InternalListIoStatementState; template class ExternalIoStatementState; template class ExternalIoStatementState; template class ExternalFormattedIoStatementState; template class ExternalFormattedIoStatementState; template class ExternalListIoStatementState; template class ExternalListIoStatementState; template class ExternalUnformattedIoStatementState; template class ExternalUnformattedIoStatementState; template class ChildIoStatementState; template class ChildIoStatementState; template class ChildFormattedIoStatementState; template class ChildFormattedIoStatementState; template class ChildListIoStatementState; template class ChildListIoStatementState; template class ChildUnformattedIoStatementState; template class ChildUnformattedIoStatementState; void ExternalMiscIoStatementState::CompleteOperation() { if (completedOperation()) { return; } ExternalFileUnit &ext{unit()}; switch (which_) { case Flush: ext.FlushOutput(*this); std::fflush(nullptr); // flushes C stdio output streams (12.9(2)) break; case Backspace: ext.BackspaceRecord(*this); break; case Endfile: ext.Endfile(*this); break; case Rewind: ext.Rewind(*this); break; case Wait: break; // handled in io-api.cpp BeginWait } return IoStatementBase::CompleteOperation(); } int ExternalMiscIoStatementState::EndIoStatement() { CompleteOperation(); return ExternalIoStatementBase::EndIoStatement(); } InquireUnitState::InquireUnitState( ExternalFileUnit &unit, const char *sourceFile, int sourceLine) : ExternalIoStatementBase{unit, sourceFile, sourceLine} {} bool InquireUnitState::Inquire( InquiryKeywordHash inquiry, char *result, std::size_t length) { if (unit().createdForInternalChildIo()) { SignalError(IostatInquireInternalUnit, "INQUIRE of unit created for defined derived type I/O of an internal " "unit"); return false; } const char *str{nullptr}; switch (inquiry) { case HashInquiryKeyword("ACCESS"): if (!unit().IsConnected()) { str = "UNDEFINED"; } else { switch (unit().access) { case Access::Sequential: str = "SEQUENTIAL"; break; case Access::Direct: str = "DIRECT"; break; case Access::Stream: str = "STREAM"; break; } } break; case HashInquiryKeyword("ACTION"): str = !unit().IsConnected() ? "UNDEFINED" : unit().mayWrite() ? unit().mayRead() ? "READWRITE" : "WRITE" : "READ"; break; case HashInquiryKeyword("ASYNCHRONOUS"): str = !unit().IsConnected() ? "UNDEFINED" : unit().mayAsynchronous() ? "YES" : "NO"; break; case HashInquiryKeyword("BLANK"): str = !unit().IsConnected() || unit().isUnformatted.value_or(true) ? "UNDEFINED" : mutableModes().editingFlags & blankZero ? "ZERO" : "NULL"; break; case HashInquiryKeyword("CARRIAGECONTROL"): str = "LIST"; break; case HashInquiryKeyword("CONVERT"): str = unit().swapEndianness() ? "SWAP" : "NATIVE"; break; case HashInquiryKeyword("DECIMAL"): str = !unit().IsConnected() || unit().isUnformatted.value_or(true) ? "UNDEFINED" : mutableModes().editingFlags & decimalComma ? "COMMA" : "POINT"; break; case HashInquiryKeyword("DELIM"): if (!unit().IsConnected() || unit().isUnformatted.value_or(true)) { str = "UNDEFINED"; } else { switch (mutableModes().delim) { case '\'': str = "APOSTROPHE"; break; case '"': str = "QUOTE"; break; default: str = "NONE"; break; } } break; case HashInquiryKeyword("DIRECT"): str = !unit().IsConnected() ? "UNKNOWN" : unit().access == Access::Direct || (unit().mayPosition() && unit().openRecl) ? "YES" : "NO"; break; case HashInquiryKeyword("ENCODING"): str = !unit().IsConnected() ? "UNKNOWN" : unit().isUnformatted.value_or(true) ? "UNDEFINED" : unit().isUTF8 ? "UTF-8" : "ASCII"; break; case HashInquiryKeyword("FORM"): str = !unit().IsConnected() || !unit().isUnformatted ? "UNDEFINED" : *unit().isUnformatted ? "UNFORMATTED" : "FORMATTED"; break; case HashInquiryKeyword("FORMATTED"): str = !unit().IsConnected() ? "UNDEFINED" : !unit().isUnformatted ? "UNKNOWN" : *unit().isUnformatted ? "NO" : "YES"; break; case HashInquiryKeyword("NAME"): str = unit().path(); if (!str) { return true; // result is undefined } break; case HashInquiryKeyword("PAD"): str = !unit().IsConnected() || unit().isUnformatted.value_or(true) ? "UNDEFINED" : mutableModes().pad ? "YES" : "NO"; break; case HashInquiryKeyword("POSITION"): if (!unit().IsConnected() || unit().access == Access::Direct) { str = "UNDEFINED"; } else { switch (unit().InquirePosition()) { case Position::Rewind: str = "REWIND"; break; case Position::Append: str = "APPEND"; break; case Position::AsIs: str = "ASIS"; break; } } break; case HashInquiryKeyword("READ"): str = !unit().IsConnected() ? "UNDEFINED" : unit().mayRead() ? "YES" : "NO"; break; case HashInquiryKeyword("READWRITE"): str = !unit().IsConnected() ? "UNDEFINED" : unit().mayRead() && unit().mayWrite() ? "YES" : "NO"; break; case HashInquiryKeyword("ROUND"): if (!unit().IsConnected() || unit().isUnformatted.value_or(true)) { str = "UNDEFINED"; } else { switch (mutableModes().round) { case decimal::FortranRounding::RoundNearest: str = "NEAREST"; break; case decimal::FortranRounding::RoundUp: str = "UP"; break; case decimal::FortranRounding::RoundDown: str = "DOWN"; break; case decimal::FortranRounding::RoundToZero: str = "ZERO"; break; case decimal::FortranRounding::RoundCompatible: str = "COMPATIBLE"; break; } } break; case HashInquiryKeyword("SEQUENTIAL"): // "NO" for Direct, since Sequential would not work if // the unit were reopened without RECL=. str = !unit().IsConnected() ? "UNKNOWN" : unit().access == Access::Sequential ? "YES" : "NO"; break; case HashInquiryKeyword("SIGN"): str = !unit().IsConnected() || unit().isUnformatted.value_or(true) ? "UNDEFINED" : mutableModes().editingFlags & signPlus ? "PLUS" : "SUPPRESS"; break; case HashInquiryKeyword("STREAM"): str = !unit().IsConnected() ? "UNKNOWN" : unit().access == Access::Stream ? "YES" : "NO"; break; case HashInquiryKeyword("UNFORMATTED"): str = !unit().IsConnected() || !unit().isUnformatted ? "UNKNOWN" : *unit().isUnformatted ? "YES" : "NO"; break; case HashInquiryKeyword("WRITE"): str = !unit().IsConnected() ? "UNKNOWN" : unit().mayWrite() ? "YES" : "NO"; break; } if (str) { ToFortranDefaultCharacter(result, length, str); return true; } else { BadInquiryKeywordHashCrash(inquiry); return false; } } bool InquireUnitState::Inquire(InquiryKeywordHash inquiry, bool &result) { switch (inquiry) { case HashInquiryKeyword("EXIST"): result = true; return true; case HashInquiryKeyword("NAMED"): result = unit().path() != nullptr; return true; case HashInquiryKeyword("OPENED"): result = unit().IsConnected(); return true; case HashInquiryKeyword("PENDING"): result = false; // asynchronous I/O is not implemented return true; default: BadInquiryKeywordHashCrash(inquiry); return false; } } bool InquireUnitState::Inquire( InquiryKeywordHash inquiry, std::int64_t, bool &result) { switch (inquiry) { case HashInquiryKeyword("PENDING"): result = false; // asynchronous I/O is not implemented return true; default: BadInquiryKeywordHashCrash(inquiry); return false; } } bool InquireUnitState::Inquire( InquiryKeywordHash inquiry, std::int64_t &result) { switch (inquiry) { case HashInquiryKeyword("NEXTREC"): if (unit().access == Access::Direct) { result = unit().currentRecordNumber; } return true; case HashInquiryKeyword("NUMBER"): result = unit().unitNumber(); return true; case HashInquiryKeyword("POS"): result = unit().InquirePos(); return true; case HashInquiryKeyword("RECL"): if (!unit().IsConnected()) { result = -1; } else if (unit().access == Access::Stream) { result = -2; } else if (unit().openRecl) { result = *unit().openRecl; } else { result = std::numeric_limits::max(); } return true; case HashInquiryKeyword("SIZE"): result = -1; if (unit().IsConnected()) { if (auto size{unit().knownSize()}) { result = *size; } } return true; default: BadInquiryKeywordHashCrash(inquiry); return false; } } InquireNoUnitState::InquireNoUnitState( const char *sourceFile, int sourceLine, int badUnitNumber) : NoUnitIoStatementState{*this, sourceFile, sourceLine, badUnitNumber} {} bool InquireNoUnitState::Inquire( InquiryKeywordHash inquiry, char *result, std::size_t length) { switch (inquiry) { case HashInquiryKeyword("ACCESS"): case HashInquiryKeyword("ACTION"): case HashInquiryKeyword("ASYNCHRONOUS"): case HashInquiryKeyword("BLANK"): case HashInquiryKeyword("CARRIAGECONTROL"): case HashInquiryKeyword("CONVERT"): case HashInquiryKeyword("DECIMAL"): case HashInquiryKeyword("DELIM"): case HashInquiryKeyword("FORM"): case HashInquiryKeyword("NAME"): case HashInquiryKeyword("PAD"): case HashInquiryKeyword("POSITION"): case HashInquiryKeyword("ROUND"): case HashInquiryKeyword("SIGN"): ToFortranDefaultCharacter(result, length, "UNDEFINED"); return true; case HashInquiryKeyword("DIRECT"): case HashInquiryKeyword("ENCODING"): case HashInquiryKeyword("FORMATTED"): case HashInquiryKeyword("READ"): case HashInquiryKeyword("READWRITE"): case HashInquiryKeyword("SEQUENTIAL"): case HashInquiryKeyword("STREAM"): case HashInquiryKeyword("WRITE"): case HashInquiryKeyword("UNFORMATTED"): ToFortranDefaultCharacter(result, length, "UNKNOWN"); return true; default: BadInquiryKeywordHashCrash(inquiry); return false; } } bool InquireNoUnitState::Inquire(InquiryKeywordHash inquiry, bool &result) { switch (inquiry) { case HashInquiryKeyword("EXIST"): result = true; return true; case HashInquiryKeyword("NAMED"): case HashInquiryKeyword("OPENED"): case HashInquiryKeyword("PENDING"): result = false; return true; default: BadInquiryKeywordHashCrash(inquiry); return false; } } bool InquireNoUnitState::Inquire( InquiryKeywordHash inquiry, std::int64_t, bool &result) { switch (inquiry) { case HashInquiryKeyword("PENDING"): result = false; return true; default: BadInquiryKeywordHashCrash(inquiry); return false; } } bool InquireNoUnitState::Inquire( InquiryKeywordHash inquiry, std::int64_t &result) { switch (inquiry) { case HashInquiryKeyword("NUMBER"): result = badUnitNumber(); return true; case HashInquiryKeyword("NEXTREC"): case HashInquiryKeyword("POS"): case HashInquiryKeyword("RECL"): case HashInquiryKeyword("SIZE"): result = -1; return true; default: BadInquiryKeywordHashCrash(inquiry); return false; } } InquireUnconnectedFileState::InquireUnconnectedFileState( OwningPtr &&path, const char *sourceFile, int sourceLine) : NoUnitIoStatementState{*this, sourceFile, sourceLine}, path_{std::move( path)} {} bool InquireUnconnectedFileState::Inquire( InquiryKeywordHash inquiry, char *result, std::size_t length) { const char *str{nullptr}; switch (inquiry) { case HashInquiryKeyword("ACCESS"): case HashInquiryKeyword("ACTION"): case HashInquiryKeyword("ASYNCHRONOUS"): case HashInquiryKeyword("BLANK"): case HashInquiryKeyword("CARRIAGECONTROL"): case HashInquiryKeyword("CONVERT"): case HashInquiryKeyword("DECIMAL"): case HashInquiryKeyword("DELIM"): case HashInquiryKeyword("FORM"): case HashInquiryKeyword("PAD"): case HashInquiryKeyword("POSITION"): case HashInquiryKeyword("ROUND"): case HashInquiryKeyword("SIGN"): str = "UNDEFINED"; break; case HashInquiryKeyword("DIRECT"): case HashInquiryKeyword("ENCODING"): case HashInquiryKeyword("FORMATTED"): case HashInquiryKeyword("SEQUENTIAL"): case HashInquiryKeyword("STREAM"): case HashInquiryKeyword("UNFORMATTED"): str = "UNKNONN"; break; case HashInquiryKeyword("READ"): str = MayRead(path_.get()) ? "YES" : "NO"; break; case HashInquiryKeyword("READWRITE"): str = MayReadAndWrite(path_.get()) ? "YES" : "NO"; break; case HashInquiryKeyword("WRITE"): str = MayWrite(path_.get()) ? "YES" : "NO"; break; case HashInquiryKeyword("NAME"): str = path_.get(); if (!str) { return true; // result is undefined } break; } if (str) { ToFortranDefaultCharacter(result, length, str); return true; } else { BadInquiryKeywordHashCrash(inquiry); return false; } } bool InquireUnconnectedFileState::Inquire( InquiryKeywordHash inquiry, bool &result) { switch (inquiry) { case HashInquiryKeyword("EXIST"): result = IsExtant(path_.get()); return true; case HashInquiryKeyword("NAMED"): result = true; return true; case HashInquiryKeyword("OPENED"): result = false; return true; case HashInquiryKeyword("PENDING"): result = false; return true; default: BadInquiryKeywordHashCrash(inquiry); return false; } } bool InquireUnconnectedFileState::Inquire( InquiryKeywordHash inquiry, std::int64_t, bool &result) { switch (inquiry) { case HashInquiryKeyword("PENDING"): result = false; return true; default: BadInquiryKeywordHashCrash(inquiry); return false; } } bool InquireUnconnectedFileState::Inquire( InquiryKeywordHash inquiry, std::int64_t &result) { switch (inquiry) { case HashInquiryKeyword("NEXTREC"): case HashInquiryKeyword("NUMBER"): case HashInquiryKeyword("POS"): case HashInquiryKeyword("RECL"): result = -1; return true; case HashInquiryKeyword("SIZE"): result = SizeInBytes(path_.get()); return true; default: BadInquiryKeywordHashCrash(inquiry); return false; } } InquireIOLengthState::InquireIOLengthState( const char *sourceFile, int sourceLine) : NoUnitIoStatementState{*this, sourceFile, sourceLine} {} bool InquireIOLengthState::Emit(const char *, std::size_t bytes, std::size_t) { bytes_ += bytes; return true; } int ErroneousIoStatementState::EndIoStatement() { SignalPendingError(); if (unit_) { unit_->EndIoStatement(); } return IoStatementBase::EndIoStatement(); } } // namespace Fortran::runtime::io