Files
clang-p2996/flang/runtime/io-stmt.cpp
V Donaldson 27d666b9ad [flang] Noncontiguous formats
Add the remaining pieces to support IO for noncontigous formats.
This is done by passing an array descriptor to IO calls.  Scalar
formats continue to pass string and length arguments.  IO calls
with formats are modified to place the new format descriptor
argument directly after the original string and length arguments.
2022-08-24 13:51:16 -07:00

1419 lines
44 KiB
C++

//===-- 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 <algorithm>
#include <cstdio>
#include <cstring>
#include <limits>
#include <type_traits>
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<DataEdit> 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;
}
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 <Direction DIR>
InternalIoStatementState<DIR>::InternalIoStatementState(
Buffer scalar, std::size_t length, const char *sourceFile, int sourceLine)
: IoStatementBase{sourceFile, sourceLine}, unit_{scalar, length, 1} {}
template <Direction DIR>
InternalIoStatementState<DIR>::InternalIoStatementState(
const Descriptor &d, const char *sourceFile, int sourceLine)
: IoStatementBase{sourceFile, sourceLine}, unit_{d, *this} {}
template <Direction DIR>
bool InternalIoStatementState<DIR>::Emit(
const char *data, std::size_t bytes, std::size_t /*elementBytes*/) {
if constexpr (DIR == Direction::Input) {
Crash("InternalIoStatementState<Direction::Input>::Emit() called");
return false;
}
return unit_.Emit(data, bytes, *this);
}
template <Direction DIR>
std::size_t InternalIoStatementState<DIR>::GetNextInputBytes(const char *&p) {
return unit_.GetNextInputBytes(p, *this);
}
template <Direction DIR>
bool InternalIoStatementState<DIR>::AdvanceRecord(int n) {
while (n-- > 0) {
if (!unit_.AdvanceRecord(*this)) {
return false;
}
}
return true;
}
template <Direction DIR> void InternalIoStatementState<DIR>::BackspaceRecord() {
unit_.BackspaceRecord(*this);
}
template <Direction DIR> int InternalIoStatementState<DIR>::EndIoStatement() {
if constexpr (DIR == Direction::Output) {
unit_.EndIoStatement(); // fill
}
auto result{IoStatementBase::EndIoStatement()};
if (free_) {
FreeMemory(this);
}
return result;
}
template <Direction DIR>
void InternalIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) {
return unit_.HandleAbsolutePosition(n);
}
template <Direction DIR>
void InternalIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) {
return unit_.HandleRelativePosition(n);
}
template <Direction DIR, typename CHAR>
InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState(
Buffer buffer, std::size_t length, const CharType *format,
std::size_t formatLength, const Descriptor *formatDescriptor,
const char *sourceFile, int sourceLine)
: InternalIoStatementState<DIR>{buffer, length, sourceFile, sourceLine},
ioStatementState_{*this}, format_{*this, format, formatLength,
formatDescriptor} {}
template <Direction DIR, typename CHAR>
InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState(
const Descriptor &d, const CharType *format, std::size_t formatLength,
const Descriptor *formatDescriptor, const char *sourceFile, int sourceLine)
: InternalIoStatementState<DIR>{d, sourceFile, sourceLine},
ioStatementState_{*this}, format_{*this, format, formatLength,
formatDescriptor} {}
template <Direction DIR, typename CHAR>
void InternalFormattedIoStatementState<DIR, CHAR>::CompleteOperation() {
if (!this->completedOperation()) {
if constexpr (DIR == Direction::Output) {
format_.Finish(*this); // ignore any remaining input positioning actions
}
IoStatementBase::CompleteOperation();
}
}
template <Direction DIR, typename CHAR>
int InternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
CompleteOperation();
return InternalIoStatementState<DIR>::EndIoStatement();
}
template <Direction DIR>
InternalListIoStatementState<DIR>::InternalListIoStatementState(
Buffer buffer, std::size_t length, const char *sourceFile, int sourceLine)
: InternalIoStatementState<DIR>{buffer, length, sourceFile, sourceLine},
ioStatementState_{*this} {}
template <Direction DIR>
InternalListIoStatementState<DIR>::InternalListIoStatementState(
const Descriptor &d, const char *sourceFile, int sourceLine)
: InternalIoStatementState<DIR>{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() {
CompleteOperation();
auto result{IoStatementBase::EndIoStatement()};
unit_.EndIoStatement(); // annihilates *this in unit_.u_
return result;
}
void ExternalIoStatementBase::SetAsynchronous() {
asynchronousID_ = unit().GetAsynchronousId(*this);
}
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 <Direction DIR>
ExternalIoStatementState<DIR>::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 <Direction DIR>
void ExternalIoStatementState<DIR>::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 <Direction DIR> int ExternalIoStatementState<DIR>::EndIoStatement() {
CompleteOperation();
return ExternalIoStatementBase::EndIoStatement();
}
template <Direction DIR>
bool ExternalIoStatementState<DIR>::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 <Direction DIR>
std::size_t ExternalIoStatementState<DIR>::GetNextInputBytes(const char *&p) {
return unit().GetNextInputBytes(p, *this);
}
template <Direction DIR>
bool ExternalIoStatementState<DIR>::AdvanceRecord(int n) {
while (n-- > 0) {
if (!unit().AdvanceRecord(*this)) {
return false;
}
}
return true;
}
template <Direction DIR> void ExternalIoStatementState<DIR>::BackspaceRecord() {
unit().BackspaceRecord(*this);
}
template <Direction DIR>
void ExternalIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) {
return unit().HandleAbsolutePosition(n);
}
template <Direction DIR>
void ExternalIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) {
return unit().HandleRelativePosition(n);
}
template <Direction DIR>
bool ExternalIoStatementState<DIR>::BeginReadingRecord() {
if constexpr (DIR == Direction::Input) {
return unit().BeginReadingRecord(*this);
} else {
Crash("ExternalIoStatementState<Direction::Output>::BeginReadingRecord() "
"called");
return false;
}
}
template <Direction DIR>
void ExternalIoStatementState<DIR>::FinishReadingRecord() {
if constexpr (DIR == Direction::Input) {
unit().FinishReadingRecord(*this);
} else {
Crash("ExternalIoStatementState<Direction::Output>::FinishReadingRecord() "
"called");
}
}
template <Direction DIR, typename CHAR>
ExternalFormattedIoStatementState<DIR, CHAR>::ExternalFormattedIoStatementState(
ExternalFileUnit &unit, const CHAR *format, std::size_t formatLength,
const Descriptor *formatDescriptor, const char *sourceFile, int sourceLine)
: ExternalIoStatementState<DIR>{unit, sourceFile, sourceLine},
format_{*this, format, formatLength, formatDescriptor} {}
template <Direction DIR, typename CHAR>
void ExternalFormattedIoStatementState<DIR, CHAR>::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<DIR>::CompleteOperation();
}
template <Direction DIR, typename CHAR>
int ExternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
CompleteOperation();
return ExternalIoStatementState<DIR>::EndIoStatement();
}
std::optional<DataEdit> 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<IoErrorHandler &>(x.get());
},
u_);
}
ExternalFileUnit *IoStatementState::GetExternalFileUnit() const {
return common::visit(
[](auto &x) { return x.get().GetExternalFileUnit(); }, u_);
}
std::optional<char32_t> 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<const char16_t *>(p);
} else {
return *reinterpret_cast<const char32_t *>(p);
}
}
byteCount = 1;
return *p;
}
}
std::optional<char32_t> IoStatementState::NextInField(
std::optional<int> &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<std::size_t>(*remaining)) {
return std::nullopt;
}
*remaining -= byteCount;
HandleRelativePosition(byteCount);
GotChar(byteCount);
return next;
}
if (CheckForEndOfRecord()) { // do padding
--*remaining;
return std::optional<char32_t>{' '};
}
}
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_);
}
void IoStatementState::GotChar(int n) {
if (auto *formattedIn{
get_if<FormattedIoStatementState<Direction::Input>>()}) {
formattedIn->GotChar(n);
} else {
GetIoErrorHandler().Crash("IoStatementState::GotChar() called for "
"statement that is not formatted input");
}
}
std::size_t
FormattedIoStatementState<Direction::Input>::GetEditDescriptorChars() const {
return chars_;
}
void FormattedIoStatementState<Direction::Input>::GotChar(int n) {
chars_ += n;
}
bool ListDirectedStatementState<Direction::Output>::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<DataEdit>
ListDirectedStatementState<Direction::Output>::GetNextDataEdit(
IoStatementState &io, int maxRepeat) {
DataEdit edit;
edit.descriptor = DataEdit::ListDirected;
edit.repeat = maxRepeat;
edit.modes = io.mutableModes();
return edit;
}
std::optional<DataEdit>
ListDirectedStatementState<Direction::Input>::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<int>(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<int>::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<int>(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 <Direction DIR>
bool ExternalUnformattedIoStatementState<DIR>::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 <Direction DIR>
ChildIoStatementState<DIR>::ChildIoStatementState(
ChildIo &child, const char *sourceFile, int sourceLine)
: IoStatementBase{sourceFile, sourceLine}, child_{child} {}
template <Direction DIR>
MutableModes &ChildIoStatementState<DIR>::mutableModes() {
return child_.parent().mutableModes();
}
template <Direction DIR>
ConnectionState &ChildIoStatementState<DIR>::GetConnectionState() {
return child_.parent().GetConnectionState();
}
template <Direction DIR>
ExternalFileUnit *ChildIoStatementState<DIR>::GetExternalFileUnit() const {
return child_.parent().GetExternalFileUnit();
}
template <Direction DIR> void ChildIoStatementState<DIR>::CompleteOperation() {
IoStatementBase::CompleteOperation();
}
template <Direction DIR> int ChildIoStatementState<DIR>::EndIoStatement() {
CompleteOperation();
auto result{IoStatementBase::EndIoStatement()};
child_.EndIoStatement(); // annihilates *this in child_.u_
return result;
}
template <Direction DIR>
bool ChildIoStatementState<DIR>::Emit(
const char *data, std::size_t bytes, std::size_t elementBytes) {
return child_.parent().Emit(data, bytes, elementBytes);
}
template <Direction DIR>
std::size_t ChildIoStatementState<DIR>::GetNextInputBytes(const char *&p) {
return child_.parent().GetNextInputBytes(p);
}
template <Direction DIR>
void ChildIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) {
return child_.parent().HandleAbsolutePosition(n);
}
template <Direction DIR>
void ChildIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) {
return child_.parent().HandleRelativePosition(n);
}
template <Direction DIR, typename CHAR>
ChildFormattedIoStatementState<DIR, CHAR>::ChildFormattedIoStatementState(
ChildIo &child, const CHAR *format, std::size_t formatLength,
const Descriptor *formatDescriptor, const char *sourceFile, int sourceLine)
: ChildIoStatementState<DIR>{child, sourceFile, sourceLine},
mutableModes_{child.parent().mutableModes()}, format_{*this, format,
formatLength,
formatDescriptor} {}
template <Direction DIR, typename CHAR>
void ChildFormattedIoStatementState<DIR, CHAR>::CompleteOperation() {
if (!this->completedOperation()) {
format_.Finish(*this);
ChildIoStatementState<DIR>::CompleteOperation();
}
}
template <Direction DIR, typename CHAR>
int ChildFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
CompleteOperation();
return ChildIoStatementState<DIR>::EndIoStatement();
}
template <Direction DIR, typename CHAR>
bool ChildFormattedIoStatementState<DIR, CHAR>::AdvanceRecord(int) {
return false; // no can do in a child I/O
}
template <Direction DIR>
bool ChildUnformattedIoStatementState<DIR>::Receive(
char *data, std::size_t bytes, std::size_t elementBytes) {
return this->child().parent().Receive(data, bytes, elementBytes);
}
template class InternalIoStatementState<Direction::Output>;
template class InternalIoStatementState<Direction::Input>;
template class InternalFormattedIoStatementState<Direction::Output>;
template class InternalFormattedIoStatementState<Direction::Input>;
template class InternalListIoStatementState<Direction::Output>;
template class InternalListIoStatementState<Direction::Input>;
template class ExternalIoStatementState<Direction::Output>;
template class ExternalIoStatementState<Direction::Input>;
template class ExternalFormattedIoStatementState<Direction::Output>;
template class ExternalFormattedIoStatementState<Direction::Input>;
template class ExternalListIoStatementState<Direction::Output>;
template class ExternalListIoStatementState<Direction::Input>;
template class ExternalUnformattedIoStatementState<Direction::Output>;
template class ExternalUnformattedIoStatementState<Direction::Input>;
template class ChildIoStatementState<Direction::Output>;
template class ChildIoStatementState<Direction::Input>;
template class ChildFormattedIoStatementState<Direction::Output>;
template class ChildFormattedIoStatementState<Direction::Input>;
template class ChildListIoStatementState<Direction::Output>;
template class ChildListIoStatementState<Direction::Input>;
template class ChildUnformattedIoStatementState<Direction::Output>;
template class ChildUnformattedIoStatementState<Direction::Input>;
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"
: unit().modes.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"
: unit().modes.editingFlags & decimalComma ? "COMMA"
: "POINT";
break;
case HashInquiryKeyword("DELIM"):
if (!unit().IsConnected() || unit().isUnformatted.value_or(true)) {
str = "UNDEFINED";
} else {
switch (unit().modes.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"
: unit().modes.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 (unit().modes.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"
: unit().modes.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<std::int32_t>::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<char> &&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