forked from OSchip/llvm-project
596 lines
19 KiB
C++
596 lines
19 KiB
C++
//===-- 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 <algorithm>
|
|
#include <cstring>
|
|
#include <limits>
|
|
|
|
namespace Fortran::runtime::io {
|
|
|
|
int IoStatementBase::EndIoStatement() { return GetIoStat(); }
|
|
|
|
std::optional<DataEdit> IoStatementBase::GetNextDataEdit(
|
|
IoStatementState &, int) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
template <Direction DIR, typename CHAR>
|
|
InternalIoStatementState<DIR, CHAR>::InternalIoStatementState(
|
|
Buffer scalar, std::size_t length, const char *sourceFile, int sourceLine)
|
|
: IoStatementBase{sourceFile, sourceLine}, unit_{scalar, length} {}
|
|
|
|
template <Direction DIR, typename CHAR>
|
|
InternalIoStatementState<DIR, CHAR>::InternalIoStatementState(
|
|
const Descriptor &d, const char *sourceFile, int sourceLine)
|
|
: IoStatementBase{sourceFile, sourceLine}, unit_{d, *this} {}
|
|
|
|
template <Direction DIR, typename CHAR>
|
|
bool InternalIoStatementState<DIR, CHAR>::Emit(
|
|
const CharType *data, std::size_t chars) {
|
|
if constexpr (DIR == Direction::Input) {
|
|
Crash("InternalIoStatementState<Direction::Input>::Emit() called");
|
|
return false;
|
|
}
|
|
return unit_.Emit(data, chars, *this);
|
|
}
|
|
|
|
template <Direction DIR, typename CHAR>
|
|
std::optional<char32_t> InternalIoStatementState<DIR, CHAR>::GetCurrentChar() {
|
|
if constexpr (DIR == Direction::Output) {
|
|
Crash(
|
|
"InternalIoStatementState<Direction::Output>::GetCurrentChar() called");
|
|
return std::nullopt;
|
|
}
|
|
return unit_.GetCurrentChar(*this);
|
|
}
|
|
|
|
template <Direction DIR, typename CHAR>
|
|
bool InternalIoStatementState<DIR, CHAR>::AdvanceRecord(int n) {
|
|
while (n-- > 0) {
|
|
if (!unit_.AdvanceRecord(*this)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <Direction DIR, typename CHAR>
|
|
void InternalIoStatementState<DIR, CHAR>::BackspaceRecord() {
|
|
unit_.BackspaceRecord(*this);
|
|
}
|
|
|
|
template <Direction DIR, typename CHAR>
|
|
int InternalIoStatementState<DIR, CHAR>::EndIoStatement() {
|
|
if constexpr (DIR == Direction::Output) {
|
|
unit_.EndIoStatement(); // fill
|
|
}
|
|
auto result{IoStatementBase::EndIoStatement()};
|
|
if (free_) {
|
|
FreeMemory(this);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template <Direction DIR, typename CHAR>
|
|
void InternalIoStatementState<DIR, CHAR>::HandleAbsolutePosition(
|
|
std::int64_t n) {
|
|
return unit_.HandleAbsolutePosition(n);
|
|
}
|
|
|
|
template <Direction DIR, typename CHAR>
|
|
void InternalIoStatementState<DIR, CHAR>::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 CHAR *format,
|
|
std::size_t formatLength, const char *sourceFile, int sourceLine)
|
|
: InternalIoStatementState<DIR, CHAR>{buffer, length, sourceFile,
|
|
sourceLine},
|
|
ioStatementState_{*this}, format_{*this, format, formatLength} {}
|
|
|
|
template <Direction DIR, typename CHAR>
|
|
InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState(
|
|
const Descriptor &d, const CHAR *format, std::size_t formatLength,
|
|
const char *sourceFile, int sourceLine)
|
|
: InternalIoStatementState<DIR, CHAR>{d, sourceFile, sourceLine},
|
|
ioStatementState_{*this}, format_{*this, format, formatLength} {}
|
|
|
|
template <Direction DIR, typename CHAR>
|
|
int InternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
|
|
if constexpr (DIR == Direction::Output) {
|
|
format_.Finish(*this); // ignore any remaining input positioning actions
|
|
}
|
|
return InternalIoStatementState<DIR, CHAR>::EndIoStatement();
|
|
}
|
|
|
|
template <Direction DIR, typename CHAR>
|
|
InternalListIoStatementState<DIR, CHAR>::InternalListIoStatementState(
|
|
Buffer buffer, std::size_t length, const char *sourceFile, int sourceLine)
|
|
: InternalIoStatementState<DIR, CharType>{buffer, length, sourceFile,
|
|
sourceLine},
|
|
ioStatementState_{*this} {}
|
|
|
|
template <Direction DIR, typename CHAR>
|
|
InternalListIoStatementState<DIR, CHAR>::InternalListIoStatementState(
|
|
const Descriptor &d, const char *sourceFile, int sourceLine)
|
|
: InternalIoStatementState<DIR, CharType>{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 <Direction DIR> int ExternalIoStatementState<DIR>::EndIoStatement() {
|
|
if constexpr (DIR == Direction::Output) {
|
|
if (!unit().nonAdvancing) {
|
|
unit().AdvanceRecord(*this);
|
|
}
|
|
unit().FlushIfTerminal(*this);
|
|
}
|
|
return ExternalIoStatementBase::EndIoStatement();
|
|
}
|
|
|
|
template <Direction DIR>
|
|
bool ExternalIoStatementState<DIR>::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 <Direction DIR>
|
|
bool ExternalIoStatementState<DIR>::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<const char *>(data), chars * sizeof(*data), *this);
|
|
}
|
|
|
|
template <Direction DIR>
|
|
bool ExternalIoStatementState<DIR>::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<const char *>(data), chars * sizeof(*data), *this);
|
|
}
|
|
|
|
template <Direction DIR>
|
|
std::optional<char32_t> ExternalIoStatementState<DIR>::GetCurrentChar() {
|
|
if constexpr (DIR == Direction::Output) {
|
|
Crash(
|
|
"ExternalIoStatementState<Direction::Output>::GetCurrentChar() called");
|
|
}
|
|
return unit().GetCurrentChar(*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, typename CHAR>
|
|
ExternalFormattedIoStatementState<DIR, CHAR>::ExternalFormattedIoStatementState(
|
|
ExternalFileUnit &unit, const CHAR *format, std::size_t formatLength,
|
|
const char *sourceFile, int sourceLine)
|
|
: ExternalIoStatementState<DIR>{unit, sourceFile, sourceLine},
|
|
mutableModes_{unit.modes}, format_{*this, format, formatLength} {}
|
|
|
|
template <Direction DIR, typename CHAR>
|
|
int ExternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
|
|
format_.Finish(*this);
|
|
return ExternalIoStatementState<DIR>::EndIoStatement();
|
|
}
|
|
|
|
std::optional<DataEdit> 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<char32_t> 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) {
|
|
return 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<IoErrorHandler &>(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<int>(length);
|
|
}
|
|
if (length > static_cast<std::size_t>(width)) {
|
|
return EmitRepeated('*', width);
|
|
} else {
|
|
return EmitRepeated(' ', static_cast<int>(width - length)) &&
|
|
Emit(p, length);
|
|
}
|
|
}
|
|
|
|
void IoStatementState::SkipSpaces(std::optional<int> &remaining) {
|
|
if (!remaining || *remaining > 0) {
|
|
for (auto ch{GetCurrentChar()}; ch && ch == ' '; ch = GetCurrentChar()) {
|
|
HandleRelativePosition(1);
|
|
if (remaining && !--*remaining) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::optional<char32_t> IoStatementState::NextInField(
|
|
std::optional<int> &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 '*':
|
|
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.recordLength &&
|
|
connection.positionInRecord >= *connection.recordLength) {
|
|
if (connection.modes.pad) { // PAD='YES'
|
|
--*remaining;
|
|
return std::optional<char32_t>{' '};
|
|
}
|
|
IoErrorHandler &handler{GetIoErrorHandler()};
|
|
if (connection.nonAdvancing) {
|
|
handler.SignalEor();
|
|
} else {
|
|
handler.SignalError(IostatRecordReadOverrun);
|
|
}
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<char32_t> 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<Direction::Output>::NeedAdvance(
|
|
const ConnectionState &connection, std::size_t width) const {
|
|
return connection.positionInRecord > 0 &&
|
|
width > connection.RemainingSpaceInRecord();
|
|
}
|
|
|
|
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)};
|
|
lastWasUndelimitedCharacter = false;
|
|
if (NeedAdvance(connection, space + length)) {
|
|
return io.AdvanceRecord();
|
|
}
|
|
if (space) {
|
|
return io.Emit(" ", 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 = 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<int>(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<int>::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<int>(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 <Direction DIR>
|
|
int UnformattedIoStatementState<DIR>::EndIoStatement() {
|
|
auto &ext{static_cast<ExternalIoStatementState<DIR> &>(*this)};
|
|
ExternalFileUnit &unit{ext.unit()};
|
|
if (unit.access == Access::Sequential && !unit.recordLength.has_value()) {
|
|
// Overwrite the first four bytes of the record with its length,
|
|
// and also append the length. 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.c;
|
|
// TODO: Convert record length to little-endian on big-endian host?
|
|
if (!(ext.Emit(u.c, sizeof u.c) &&
|
|
(ext.HandleAbsolutePosition(0), ext.Emit(u.c, sizeof u.c)) &&
|
|
ext.AdvanceRecord())) {
|
|
return false;
|
|
}
|
|
}
|
|
return ext.EndIoStatement();
|
|
}
|
|
|
|
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 UnformattedIoStatementState<Direction::Output>;
|
|
template class UnformattedIoStatementState<Direction::Input>;
|
|
} // namespace Fortran::runtime::io
|