//===-- runtime/io-stmt.cpp -------------------------------------*- C++ -*-===// // // 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 "format.h" #include "memory.h" #include "tools.h" #include "unit.h" #include #include #include #include namespace Fortran::runtime::io { int IoStatementBase::EndIoStatement() { return GetIoStat(); } std::optional IoStatementBase::GetNextDataEdit( IoStatementState &, int) { return std::nullopt; } template InternalIoStatementState::InternalIoStatementState( Buffer scalar, std::size_t length, const char *sourceFile, int sourceLine) : IoStatementBase{sourceFile, sourceLine}, unit_{scalar, length} {} template InternalIoStatementState::InternalIoStatementState( const Descriptor &d, const char *sourceFile, int sourceLine) : IoStatementBase{sourceFile, sourceLine}, unit_{d, *this} {} template bool InternalIoStatementState::Emit( const CharType *data, std::size_t chars) { if constexpr (DIR == Direction::Input) { Crash("InternalIoStatementState::Emit() called"); return false; } return unit_.Emit(data, chars, *this); } template std::optional InternalIoStatementState::GetCurrentChar() { if constexpr (DIR == Direction::Output) { Crash( "InternalIoStatementState::GetCurrentChar() called"); return std::nullopt; } return unit_.GetCurrentChar(*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 InternalFormattedIoStatementState::InternalFormattedIoStatementState( Buffer buffer, std::size_t length, const CHAR *format, std::size_t formatLength, const char *sourceFile, int sourceLine) : InternalIoStatementState{buffer, length, sourceFile, sourceLine}, ioStatementState_{*this}, format_{*this, format, formatLength} {} template InternalFormattedIoStatementState::InternalFormattedIoStatementState( const Descriptor &d, const CHAR *format, std::size_t formatLength, const char *sourceFile, int sourceLine) : InternalIoStatementState{d, sourceFile, sourceLine}, ioStatementState_{*this}, format_{*this, format, formatLength} {} template int InternalFormattedIoStatementState::EndIoStatement() { if constexpr (DIR == Direction::Output) { format_.Finish(*this); // ignore any remaining input positioning actions } 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() { return unit_.modes; } ConnectionState &ExternalIoStatementBase::GetConnectionState() { return unit_; } int ExternalIoStatementBase::EndIoStatement() { if (unit_.nonAdvancing) { unit_.leftTabLimit = unit_.furthestPositionInRecord; unit_.nonAdvancing = false; } else { unit_.leftTabLimit.reset(); } auto result{IoStatementBase::EndIoStatement()}; unit_.EndIoStatement(); // annihilates *this in unit_.u_ return result; } void OpenStatementState::set_path( const char *path, std::size_t length, int kind) { if (kind != 1) { // TODO Crash("OPEN: FILE= with unimplemented: CHARACTER(KIND=%d)", kind); } std::size_t bytes{length * kind}; // TODO: UTF-8 encoding of Unicode path path_ = SaveDefaultCharacter(path, bytes, *this); pathLength_ = length; } int OpenStatementState::EndIoStatement() { if (wasExtant_ && status_ != OpenStatus::Old) { SignalError("OPEN statement for connected unit must have STATUS='OLD'"); } unit().OpenUnit(status_, position_, std::move(path_), pathLength_, *this); return ExternalIoStatementBase::EndIoStatement(); } int CloseStatementState::EndIoStatement() { int result{ExternalIoStatementBase::EndIoStatement()}; unit().CloseUnit(status_, *this); unit().DestroyClosed(); return result; } int NoopCloseStatementState::EndIoStatement() { auto result{IoStatementBase::EndIoStatement()}; FreeMemory(this); return result; } template int ExternalIoStatementState::EndIoStatement() { if (!unit().nonAdvancing) { unit().AdvanceRecord(*this); } if constexpr (DIR == Direction::Output) { unit().FlushIfTerminal(*this); } return ExternalIoStatementBase::EndIoStatement(); } template bool ExternalIoStatementState::Emit(const char *data, std::size_t chars) { if constexpr (DIR == Direction::Input) { Crash("ExternalIoStatementState::Emit(char) called for input statement"); } return unit().Emit(data, chars * sizeof(*data), *this); } template bool ExternalIoStatementState::Emit( const char16_t *data, std::size_t chars) { if constexpr (DIR == Direction::Input) { Crash( "ExternalIoStatementState::Emit(char16_t) called for input statement"); } // TODO: UTF-8 encoding return unit().Emit( reinterpret_cast(data), chars * sizeof(*data), *this); } template bool ExternalIoStatementState::Emit( const char32_t *data, std::size_t chars) { if constexpr (DIR == Direction::Input) { Crash( "ExternalIoStatementState::Emit(char32_t) called for input statement"); } // TODO: UTF-8 encoding return unit().Emit( reinterpret_cast(data), chars * sizeof(*data), *this); } template std::optional ExternalIoStatementState::GetCurrentChar() { if constexpr (DIR == Direction::Output) { Crash( "ExternalIoStatementState::GetCurrentChar() called"); } return unit().GetCurrentChar(*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 ExternalFormattedIoStatementState::ExternalFormattedIoStatementState( ExternalFileUnit &unit, const CHAR *format, std::size_t formatLength, const char *sourceFile, int sourceLine) : ExternalIoStatementState{unit, sourceFile, sourceLine}, mutableModes_{unit.modes}, format_{*this, format, formatLength} {} template int ExternalFormattedIoStatementState::EndIoStatement() { format_.Finish(*this); return ExternalIoStatementState::EndIoStatement(); } std::optional IoStatementState::GetNextDataEdit(int n) { return std::visit( [&](auto &x) { return x.get().GetNextDataEdit(*this, n); }, u_); } bool IoStatementState::Emit(const char *data, std::size_t n) { return std::visit([=](auto &x) { return x.get().Emit(data, n); }, u_); } std::optional IoStatementState::GetCurrentChar() { return std::visit([&](auto &x) { return x.get().GetCurrentChar(); }, u_); } bool IoStatementState::AdvanceRecord(int n) { return std::visit([=](auto &x) { return x.get().AdvanceRecord(n); }, u_); } void IoStatementState::BackspaceRecord() { std::visit([](auto &x) { x.get().BackspaceRecord(); }, u_); } void IoStatementState::HandleRelativePosition(std::int64_t n) { std::visit([=](auto &x) { x.get().HandleRelativePosition(n); }, u_); } int IoStatementState::EndIoStatement() { return std::visit([](auto &x) { return x.get().EndIoStatement(); }, u_); } ConnectionState &IoStatementState::GetConnectionState() { return std::visit( [](auto &x) -> ConnectionState & { return x.get().GetConnectionState(); }, u_); } MutableModes &IoStatementState::mutableModes() { return std::visit( [](auto &x) -> MutableModes & { return x.get().mutableModes(); }, u_); } IoErrorHandler &IoStatementState::GetIoErrorHandler() const { return std::visit( [](auto &x) -> IoErrorHandler & { return static_cast(x.get()); }, u_); } ExternalFileUnit *IoStatementState::GetExternalFileUnit() const { return std::visit([](auto &x) { return x.get().GetExternalFileUnit(); }, u_); } bool IoStatementState::EmitRepeated(char ch, std::size_t n) { return std::visit( [=](auto &x) { for (std::size_t j{0}; j < n; ++j) { if (!x.get().Emit(&ch, 1)) { return false; } } return true; }, u_); } bool IoStatementState::EmitField( const char *p, std::size_t length, std::size_t width) { if (width <= 0) { width = static_cast(length); } if (length > static_cast(width)) { return EmitRepeated('*', width); } else { return EmitRepeated(' ', static_cast(width - length)) && Emit(p, length); } } std::optional IoStatementState::SkipSpaces( std::optional &remaining) { while (!remaining || *remaining > 0) { if (auto ch{GetCurrentChar()}) { if (*ch != ' ') { return ch; } HandleRelativePosition(1); if (remaining) { --*remaining; } } else { break; } } return std::nullopt; } std::optional IoStatementState::NextInField( std::optional &remaining) { if (!remaining) { // list-directed or namelist: check for separators if (auto next{GetCurrentChar()}) { switch (*next) { case ' ': 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); return next; } const ConnectionState &connection{GetConnectionState()}; if (!connection.IsAtEOF() && connection.isFixedRecordLength && connection.recordLength && connection.positionInRecord >= *connection.recordLength) { if (connection.modes.pad) { // PAD='YES' --*remaining; return std::optional{' '}; } IoErrorHandler &handler{GetIoErrorHandler()}; if (connection.nonAdvancing) { handler.SignalEor(); } else { handler.SignalError(IostatRecordReadOverrun); } } } return std::nullopt; } std::optional IoStatementState::GetNextNonBlank() { auto ch{GetCurrentChar()}; while (ch.value_or(' ') == ' ') { if (ch) { HandleRelativePosition(1); } else if (!AdvanceRecord()) { return std::nullopt; } ch = GetCurrentChar(); } return ch; } bool ListDirectedStatementState::NeedAdvance( const ConnectionState &connection, std::size_t width) const { return connection.positionInRecord > 0 && width > connection.RemainingSpaceInRecord(); } 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)}; lastWasUndelimitedCharacter = false; if (NeedAdvance(connection, space + length)) { return io.AdvanceRecord(); } if (space) { return io.Emit(" ", 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 = connection.modes; if (hitSlash_) { // everything after '/' is nullified edit.descriptor = DataEdit::ListDirectedNullValue; return edit; } if (remaining_ > 0 && !realPart_) { // "r*c" repetition in progress while (connection.currentRecordNumber > initialRecordNumber_) { io.BackspaceRecord(); } connection.HandleAbsolutePosition(initialPositionInRecord_); if (!imaginaryPart_) { edit.repeat = std::min(remaining_, maxRepeat); } remaining_ -= edit.repeat; return edit; } // Skip separators, handle a "r*c" repeat count; see 13.10.2 in Fortran 2018 auto ch{io.GetNextNonBlank()}; if (imaginaryPart_) { imaginaryPart_ = false; if (ch && *ch == ')') { io.HandleRelativePosition(1); ch = io.GetNextNonBlank(); } } else if (realPart_) { realPart_ = false; imaginaryPart_ = true; } if (!ch) { return std::nullopt; } if (*ch == '/') { hitSlash_ = true; edit.descriptor = DataEdit::ListDirectedNullValue; return edit; } char32_t comma{','}; if (io.mutableModes().editingFlags & decimalComma) { comma = ';'; } bool isFirstItem{isFirstItem_}; isFirstItem_ = false; if (*ch == comma) { if (isFirstItem) { edit.descriptor = DataEdit::ListDirectedNullValue; return edit; } // Consume comma & whitespace after previous item. io.HandleRelativePosition(1); ch = io.GetNextNonBlank(); if (!ch) { return std::nullopt; } if (*ch == comma || *ch == '/') { 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(1); ch = io.GetCurrentChar(); } while (ch && *ch >= '0' && *ch <= '9'); if (r > 0 && ch && *ch == '*') { // subtle: r must be nonzero io.HandleRelativePosition(1); ch = io.GetCurrentChar(); if (!ch || *ch == ' ' || *ch == comma || *ch == '/') { // "r*" null edit.descriptor = DataEdit::ListDirectedNullValue; return edit; } edit.repeat = std::min(r, maxRepeat); remaining_ = r - edit.repeat; initialRecordNumber_ = connection.currentRecordNumber; initialPositionInRecord_ = connection.positionInRecord; } else { // not a repetition count, just an integer value; rewind connection.positionInRecord = start; } } if (!imaginaryPart_ && ch && *ch == '(') { realPart_ = true; io.HandleRelativePosition(1); } return edit; } template bool UnformattedIoStatementState::Receive(char *data, std::size_t bytes) { if constexpr (DIR == Direction::Output) { this->Crash( "UnformattedIoStatementState::Receive() called for output statement"); } return this->unit().Receive(data, bytes, *this); } template int UnformattedIoStatementState::EndIoStatement() { ExternalFileUnit &unit{this->unit()}; if constexpr (DIR == Direction::Output) { if (unit.access == Access::Sequential && !unit.isFixedRecordLength) { // 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 // over in BeginUnformattedOutput(). // TODO: Break very large records up into subrecords with negative // headers &/or footers union { std::uint32_t u; char c[sizeof u]; } u; u.u = unit.furthestPositionInRecord - sizeof u; // TODO: Convert record length to little-endian on big-endian host? if (!(this->Emit(u.c, sizeof u) && (this->HandleAbsolutePosition(0), this->Emit(u.c, sizeof u)))) { return false; } } } return ExternalIoStatementState::EndIoStatement(); } 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 UnformattedIoStatementState; template class UnformattedIoStatementState; int ExternalMiscIoStatementState::EndIoStatement() { ExternalFileUnit &ext{unit()}; switch (which_) { case Flush: ext.Flush(*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; } return ExternalIoStatementBase::EndIoStatement(); } } // namespace Fortran::runtime::io