forked from OSchip/llvm-project
904 lines
29 KiB
C++
904 lines
29 KiB
C++
//===-- runtime/io-api.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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Implements the I/O statement API
|
|
|
|
#include "io-api.h"
|
|
#include "edit-input.h"
|
|
#include "edit-output.h"
|
|
#include "environment.h"
|
|
#include "format.h"
|
|
#include "io-stmt.h"
|
|
#include "memory.h"
|
|
#include "terminator.h"
|
|
#include "tools.h"
|
|
#include "unit.h"
|
|
#include <cstdlib>
|
|
#include <memory>
|
|
|
|
namespace Fortran::runtime::io {
|
|
|
|
template <Direction DIR>
|
|
Cookie BeginInternalArrayListIO(const Descriptor &descriptor,
|
|
void ** /*scratchArea*/, std::size_t /*scratchBytes*/,
|
|
const char *sourceFile, int sourceLine) {
|
|
Terminator oom{sourceFile, sourceLine};
|
|
return &New<InternalListIoStatementState<DIR>>{}(
|
|
oom, descriptor, sourceFile, sourceLine)
|
|
.ioStatementState();
|
|
}
|
|
|
|
Cookie IONAME(BeginInternalArrayListOutput)(const Descriptor &descriptor,
|
|
void **scratchArea, std::size_t scratchBytes, const char *sourceFile,
|
|
int sourceLine) {
|
|
return BeginInternalArrayListIO<Direction::Output>(
|
|
descriptor, scratchArea, scratchBytes, sourceFile, sourceLine);
|
|
}
|
|
|
|
Cookie IONAME(BeginInternalArrayListInput)(const Descriptor &descriptor,
|
|
void **scratchArea, std::size_t scratchBytes, const char *sourceFile,
|
|
int sourceLine) {
|
|
return BeginInternalArrayListIO<Direction::Input>(
|
|
descriptor, scratchArea, scratchBytes, sourceFile, sourceLine);
|
|
}
|
|
|
|
template <Direction DIR>
|
|
Cookie BeginInternalArrayFormattedIO(const Descriptor &descriptor,
|
|
const char *format, std::size_t formatLength, void ** /*scratchArea*/,
|
|
std::size_t /*scratchBytes*/, const char *sourceFile, int sourceLine) {
|
|
Terminator oom{sourceFile, sourceLine};
|
|
return &New<InternalFormattedIoStatementState<DIR>>{}(
|
|
oom, descriptor, format, formatLength, sourceFile, sourceLine)
|
|
.ioStatementState();
|
|
}
|
|
|
|
Cookie IONAME(BeginInternalArrayFormattedOutput)(const Descriptor &descriptor,
|
|
const char *format, std::size_t formatLength, void **scratchArea,
|
|
std::size_t scratchBytes, const char *sourceFile, int sourceLine) {
|
|
return BeginInternalArrayFormattedIO<Direction::Output>(descriptor, format,
|
|
formatLength, scratchArea, scratchBytes, sourceFile, sourceLine);
|
|
}
|
|
|
|
Cookie IONAME(BeginInternalArrayFormattedInput)(const Descriptor &descriptor,
|
|
const char *format, std::size_t formatLength, void **scratchArea,
|
|
std::size_t scratchBytes, const char *sourceFile, int sourceLine) {
|
|
return BeginInternalArrayFormattedIO<Direction::Input>(descriptor, format,
|
|
formatLength, scratchArea, scratchBytes, sourceFile, sourceLine);
|
|
}
|
|
|
|
template <Direction DIR>
|
|
Cookie BeginInternalFormattedIO(
|
|
std::conditional_t<DIR == Direction::Input, const char, char> *internal,
|
|
std::size_t internalLength, const char *format, std::size_t formatLength,
|
|
void ** /*scratchArea*/, std::size_t /*scratchBytes*/,
|
|
const char *sourceFile, int sourceLine) {
|
|
Terminator oom{sourceFile, sourceLine};
|
|
return &New<InternalFormattedIoStatementState<DIR>>{}(oom, internal,
|
|
internalLength, format, formatLength, sourceFile, sourceLine)
|
|
.ioStatementState();
|
|
}
|
|
|
|
Cookie IONAME(BeginInternalFormattedOutput)(char *internal,
|
|
std::size_t internalLength, const char *format, std::size_t formatLength,
|
|
void **scratchArea, std::size_t scratchBytes, const char *sourceFile,
|
|
int sourceLine) {
|
|
Terminator oom{sourceFile, sourceLine};
|
|
return BeginInternalFormattedIO<Direction::Output>(internal, internalLength,
|
|
format, formatLength, scratchArea, scratchBytes, sourceFile, sourceLine);
|
|
}
|
|
|
|
Cookie IONAME(BeginInternalFormattedInput)(const char *internal,
|
|
std::size_t internalLength, const char *format, std::size_t formatLength,
|
|
void **scratchArea, std::size_t scratchBytes, const char *sourceFile,
|
|
int sourceLine) {
|
|
Terminator oom{sourceFile, sourceLine};
|
|
return BeginInternalFormattedIO<Direction::Input>(internal, internalLength,
|
|
format, formatLength, scratchArea, scratchBytes, sourceFile, sourceLine);
|
|
}
|
|
|
|
template <Direction DIR>
|
|
Cookie BeginExternalListIO(
|
|
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
|
|
Terminator terminator{sourceFile, sourceLine};
|
|
if (unitNumber == DefaultUnit) {
|
|
unitNumber = DIR == Direction::Input ? 5 : 6;
|
|
}
|
|
ExternalFileUnit &unit{
|
|
ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)};
|
|
if (unit.access == Access::Direct) {
|
|
terminator.Crash("List-directed I/O attempted on direct access file");
|
|
return nullptr;
|
|
}
|
|
if (unit.isUnformatted) {
|
|
terminator.Crash("List-directed I/O attempted on unformatted file");
|
|
return nullptr;
|
|
}
|
|
IoStatementState &io{unit.BeginIoStatement<ExternalListIoStatementState<DIR>>(
|
|
unit, sourceFile, sourceLine)};
|
|
if constexpr (DIR == Direction::Input) {
|
|
io.AdvanceRecord();
|
|
}
|
|
return &io;
|
|
}
|
|
|
|
Cookie IONAME(BeginExternalListOutput)(
|
|
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
|
|
return BeginExternalListIO<Direction::Output>(
|
|
unitNumber, sourceFile, sourceLine);
|
|
}
|
|
|
|
Cookie IONAME(BeginExternalListInput)(
|
|
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
|
|
return BeginExternalListIO<Direction::Input>(
|
|
unitNumber, sourceFile, sourceLine);
|
|
}
|
|
|
|
template <Direction DIR>
|
|
Cookie BeginExternalFormattedIO(const char *format, std::size_t formatLength,
|
|
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
|
|
Terminator terminator{sourceFile, sourceLine};
|
|
if (unitNumber == DefaultUnit) {
|
|
unitNumber = DIR == Direction::Input ? 5 : 6;
|
|
}
|
|
ExternalFileUnit &unit{
|
|
ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)};
|
|
if (unit.isUnformatted) {
|
|
terminator.Crash("Formatted I/O attempted on unformatted file");
|
|
return nullptr;
|
|
}
|
|
IoStatementState &io{
|
|
unit.BeginIoStatement<ExternalFormattedIoStatementState<DIR>>(
|
|
unit, format, formatLength, sourceFile, sourceLine)};
|
|
if constexpr (DIR == Direction::Input) {
|
|
io.AdvanceRecord();
|
|
}
|
|
return &io;
|
|
}
|
|
|
|
Cookie IONAME(BeginExternalFormattedOutput)(const char *format,
|
|
std::size_t formatLength, ExternalUnit unitNumber, const char *sourceFile,
|
|
int sourceLine) {
|
|
return BeginExternalFormattedIO<Direction::Output>(
|
|
format, formatLength, unitNumber, sourceFile, sourceLine);
|
|
}
|
|
|
|
Cookie IONAME(BeginExternalFormattedInput)(const char *format,
|
|
std::size_t formatLength, ExternalUnit unitNumber, const char *sourceFile,
|
|
int sourceLine) {
|
|
return BeginExternalFormattedIO<Direction::Input>(
|
|
format, formatLength, unitNumber, sourceFile, sourceLine);
|
|
}
|
|
|
|
template <Direction DIR>
|
|
Cookie BeginUnformattedIO(
|
|
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
|
|
Terminator terminator{sourceFile, sourceLine};
|
|
ExternalFileUnit &file{
|
|
ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)};
|
|
if (!file.isUnformatted) {
|
|
terminator.Crash("Unformatted output attempted on formatted file");
|
|
}
|
|
IoStatementState &io{file.BeginIoStatement<UnformattedIoStatementState<DIR>>(
|
|
file, sourceFile, sourceLine)};
|
|
if constexpr (DIR == Direction::Input) {
|
|
io.AdvanceRecord();
|
|
} else {
|
|
if (file.access == Access::Sequential && !file.recordLength.has_value()) {
|
|
// Create space for (sub)record header to be completed by
|
|
// UnformattedIoStatementState<Direction::Output>::EndIoStatement()
|
|
io.Emit("\0\0\0\0", 4); // placeholder for record length header
|
|
}
|
|
}
|
|
return &io;
|
|
}
|
|
|
|
Cookie IONAME(BeginUnformattedOutput)(
|
|
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
|
|
return BeginUnformattedIO<Direction::Output>(
|
|
unitNumber, sourceFile, sourceLine);
|
|
}
|
|
|
|
Cookie IONAME(BeginUnformattedInput)(
|
|
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
|
|
return BeginUnformattedIO<Direction::Input>(
|
|
unitNumber, sourceFile, sourceLine);
|
|
}
|
|
|
|
Cookie IONAME(BeginOpenUnit)( // OPEN(without NEWUNIT=)
|
|
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
|
|
bool wasExtant{false};
|
|
Terminator terminator{sourceFile, sourceLine};
|
|
ExternalFileUnit &unit{
|
|
ExternalFileUnit::LookUpOrCreate(unitNumber, terminator, &wasExtant)};
|
|
return &unit.BeginIoStatement<OpenStatementState>(
|
|
unit, wasExtant, sourceFile, sourceLine);
|
|
}
|
|
|
|
Cookie IONAME(BeginOpenNewUnit)( // OPEN(NEWUNIT=j)
|
|
const char *sourceFile, int sourceLine) {
|
|
Terminator terminator{sourceFile, sourceLine};
|
|
return IONAME(BeginOpenUnit)(
|
|
ExternalFileUnit::NewUnit(terminator), sourceFile, sourceLine);
|
|
}
|
|
|
|
Cookie IONAME(BeginClose)(
|
|
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
|
|
if (ExternalFileUnit * unit{ExternalFileUnit::LookUpForClose(unitNumber)}) {
|
|
return &unit->BeginIoStatement<CloseStatementState>(
|
|
*unit, sourceFile, sourceLine);
|
|
} else {
|
|
// CLOSE(UNIT=bad unit) is just a no-op
|
|
Terminator oom{sourceFile, sourceLine};
|
|
return &New<NoopCloseStatementState>{}(oom, sourceFile, sourceLine)
|
|
.ioStatementState();
|
|
}
|
|
}
|
|
|
|
// Control list items
|
|
|
|
void IONAME(EnableHandlers)(Cookie cookie, bool hasIoStat, bool hasErr,
|
|
bool hasEnd, bool hasEor, bool hasIoMsg) {
|
|
IoErrorHandler &handler{cookie->GetIoErrorHandler()};
|
|
if (hasIoStat) {
|
|
handler.HasIoStat();
|
|
}
|
|
if (hasErr) {
|
|
handler.HasErrLabel();
|
|
}
|
|
if (hasEnd) {
|
|
handler.HasEndLabel();
|
|
}
|
|
if (hasEor) {
|
|
handler.HasEorLabel();
|
|
}
|
|
if (hasIoMsg) {
|
|
handler.HasIoMsg();
|
|
}
|
|
}
|
|
|
|
static bool YesOrNo(const char *keyword, std::size_t length, const char *what,
|
|
IoErrorHandler &handler) {
|
|
static const char *keywords[]{"YES", "NO", nullptr};
|
|
switch (IdentifyValue(keyword, length, keywords)) {
|
|
case 0:
|
|
return true;
|
|
case 1:
|
|
return false;
|
|
default:
|
|
handler.SignalError(IostatErrorInKeyword, "Invalid %s='%.*s'", what,
|
|
static_cast<int>(length), keyword);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IONAME(SetAdvance)(
|
|
Cookie cookie, const char *keyword, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
ConnectionState &connection{io.GetConnectionState()};
|
|
connection.nonAdvancing =
|
|
!YesOrNo(keyword, length, "ADVANCE", io.GetIoErrorHandler());
|
|
if (connection.nonAdvancing && connection.access == Access::Direct) {
|
|
io.GetIoErrorHandler().SignalError(
|
|
"Non-advancing I/O attempted on direct access file");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IONAME(SetBlank)(Cookie cookie, const char *keyword, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
ConnectionState &connection{io.GetConnectionState()};
|
|
static const char *keywords[]{"NULL", "ZERO", nullptr};
|
|
switch (IdentifyValue(keyword, length, keywords)) {
|
|
case 0:
|
|
connection.modes.editingFlags &= ~blankZero;
|
|
return true;
|
|
case 1:
|
|
connection.modes.editingFlags |= blankZero;
|
|
return true;
|
|
default:
|
|
io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
|
|
"Invalid BLANK='%.*s'", static_cast<int>(length), keyword);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IONAME(SetDecimal)(
|
|
Cookie cookie, const char *keyword, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
ConnectionState &connection{io.GetConnectionState()};
|
|
static const char *keywords[]{"COMMA", "POINT", nullptr};
|
|
switch (IdentifyValue(keyword, length, keywords)) {
|
|
case 0:
|
|
connection.modes.editingFlags |= decimalComma;
|
|
return true;
|
|
case 1:
|
|
connection.modes.editingFlags &= ~decimalComma;
|
|
return true;
|
|
default:
|
|
io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
|
|
"Invalid DECIMAL='%.*s'", static_cast<int>(length), keyword);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IONAME(SetDelim)(Cookie cookie, const char *keyword, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
ConnectionState &connection{io.GetConnectionState()};
|
|
static const char *keywords[]{"APOSTROPHE", "QUOTE", "NONE", nullptr};
|
|
switch (IdentifyValue(keyword, length, keywords)) {
|
|
case 0:
|
|
connection.modes.delim = '\'';
|
|
return true;
|
|
case 1:
|
|
connection.modes.delim = '"';
|
|
return true;
|
|
case 2:
|
|
connection.modes.delim = '\0';
|
|
return true;
|
|
default:
|
|
io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
|
|
"Invalid DELIM='%.*s'", static_cast<int>(length), keyword);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IONAME(SetPad)(Cookie cookie, const char *keyword, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
ConnectionState &connection{io.GetConnectionState()};
|
|
connection.modes.pad =
|
|
YesOrNo(keyword, length, "PAD", io.GetIoErrorHandler());
|
|
return true;
|
|
}
|
|
|
|
bool IONAME(SetPos)(Cookie cookie, std::int64_t pos) {
|
|
IoStatementState &io{*cookie};
|
|
ConnectionState &connection{io.GetConnectionState()};
|
|
if (connection.access != Access::Stream) {
|
|
io.GetIoErrorHandler().SignalError(
|
|
"REC= may not appear unless ACCESS='STREAM'");
|
|
return false;
|
|
}
|
|
if (pos < 1) {
|
|
io.GetIoErrorHandler().SignalError(
|
|
"POS=%zd is invalid", static_cast<std::intmax_t>(pos));
|
|
return false;
|
|
}
|
|
if (auto *unit{io.GetExternalFileUnit()}) {
|
|
unit->SetPosition(pos);
|
|
return true;
|
|
}
|
|
io.GetIoErrorHandler().Crash("SetPos() on internal unit");
|
|
return false;
|
|
}
|
|
|
|
bool IONAME(SetRec)(Cookie cookie, std::int64_t rec) {
|
|
IoStatementState &io{*cookie};
|
|
ConnectionState &connection{io.GetConnectionState()};
|
|
if (connection.access != Access::Direct) {
|
|
io.GetIoErrorHandler().SignalError(
|
|
"REC= may not appear unless ACCESS='DIRECT'");
|
|
return false;
|
|
}
|
|
if (!connection.recordLength) {
|
|
io.GetIoErrorHandler().SignalError("RECL= was not specified");
|
|
return false;
|
|
}
|
|
if (rec < 1) {
|
|
io.GetIoErrorHandler().SignalError(
|
|
"REC=%zd is invalid", static_cast<std::intmax_t>(rec));
|
|
return false;
|
|
}
|
|
connection.currentRecordNumber = rec;
|
|
if (auto *unit{io.GetExternalFileUnit()}) {
|
|
unit->SetPosition(rec * *connection.recordLength);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IONAME(SetRound)(Cookie cookie, const char *keyword, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
ConnectionState &connection{io.GetConnectionState()};
|
|
static const char *keywords[]{"UP", "DOWN", "ZERO", "NEAREST", "COMPATIBLE",
|
|
"PROCESSOR_DEFINED", nullptr};
|
|
switch (IdentifyValue(keyword, length, keywords)) {
|
|
case 0:
|
|
connection.modes.round = decimal::RoundUp;
|
|
return true;
|
|
case 1:
|
|
connection.modes.round = decimal::RoundDown;
|
|
return true;
|
|
case 2:
|
|
connection.modes.round = decimal::RoundToZero;
|
|
return true;
|
|
case 3:
|
|
connection.modes.round = decimal::RoundNearest;
|
|
return true;
|
|
case 4:
|
|
connection.modes.round = decimal::RoundCompatible;
|
|
return true;
|
|
case 5:
|
|
connection.modes.round = executionEnvironment.defaultOutputRoundingMode;
|
|
return true;
|
|
default:
|
|
io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
|
|
"Invalid ROUND='%.*s'", static_cast<int>(length), keyword);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IONAME(SetSign)(Cookie cookie, const char *keyword, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
ConnectionState &connection{io.GetConnectionState()};
|
|
static const char *keywords[]{"PLUS", "YES", "PROCESSOR_DEFINED", nullptr};
|
|
switch (IdentifyValue(keyword, length, keywords)) {
|
|
case 0:
|
|
connection.modes.editingFlags |= signPlus;
|
|
return true;
|
|
case 1:
|
|
case 2: // processor default is SS
|
|
connection.modes.editingFlags &= ~signPlus;
|
|
return true;
|
|
default:
|
|
io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
|
|
"Invalid SIGN='%.*s'", static_cast<int>(length), keyword);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IONAME(SetAccess)(Cookie cookie, const char *keyword, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
auto *open{io.get_if<OpenStatementState>()};
|
|
if (!open) {
|
|
io.GetIoErrorHandler().Crash(
|
|
"SetAccess() called when not in an OPEN statement");
|
|
}
|
|
ConnectionState &connection{open->GetConnectionState()};
|
|
Access access{connection.access};
|
|
static const char *keywords[]{"SEQUENTIAL", "DIRECT", "STREAM", nullptr};
|
|
switch (IdentifyValue(keyword, length, keywords)) {
|
|
case 0:
|
|
access = Access::Sequential;
|
|
break;
|
|
case 1:
|
|
access = Access::Direct;
|
|
break;
|
|
case 2:
|
|
access = Access::Stream;
|
|
break;
|
|
default:
|
|
open->SignalError(IostatErrorInKeyword, "Invalid ACCESS='%.*s'",
|
|
static_cast<int>(length), keyword);
|
|
}
|
|
if (access != connection.access) {
|
|
if (open->wasExtant()) {
|
|
open->SignalError("ACCESS= may not be changed on an open unit");
|
|
}
|
|
connection.access = access;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IONAME(SetAction)(Cookie cookie, const char *keyword, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
auto *open{io.get_if<OpenStatementState>()};
|
|
if (!open) {
|
|
io.GetIoErrorHandler().Crash(
|
|
"SetAction() called when not in an OPEN statement");
|
|
}
|
|
bool mayRead{true};
|
|
bool mayWrite{true};
|
|
static const char *keywords[]{"READ", "WRITE", "READWRITE", nullptr};
|
|
switch (IdentifyValue(keyword, length, keywords)) {
|
|
case 0:
|
|
mayWrite = false;
|
|
break;
|
|
case 1:
|
|
mayRead = false;
|
|
break;
|
|
case 2:
|
|
break;
|
|
default:
|
|
open->SignalError(IostatErrorInKeyword, "Invalid ACTION='%.*s'",
|
|
static_cast<int>(length), keyword);
|
|
return false;
|
|
}
|
|
if (mayRead != open->unit().mayRead() ||
|
|
mayWrite != open->unit().mayWrite()) {
|
|
if (open->wasExtant()) {
|
|
open->SignalError("ACTION= may not be changed on an open unit");
|
|
}
|
|
open->unit().set_mayRead(mayRead);
|
|
open->unit().set_mayWrite(mayWrite);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IONAME(SetAsynchronous)(
|
|
Cookie cookie, const char *keyword, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
auto *open{io.get_if<OpenStatementState>()};
|
|
if (!open) {
|
|
io.GetIoErrorHandler().Crash(
|
|
"SetAsynchronous() called when not in an OPEN statement");
|
|
}
|
|
static const char *keywords[]{"YES", "NO", nullptr};
|
|
switch (IdentifyValue(keyword, length, keywords)) {
|
|
case 0:
|
|
open->unit().set_mayAsynchronous(true);
|
|
return true;
|
|
case 1:
|
|
open->unit().set_mayAsynchronous(false);
|
|
return true;
|
|
default:
|
|
open->SignalError(IostatErrorInKeyword, "Invalid ASYNCHRONOUS='%.*s'",
|
|
static_cast<int>(length), keyword);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IONAME(SetEncoding)(
|
|
Cookie cookie, const char *keyword, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
auto *open{io.get_if<OpenStatementState>()};
|
|
if (!open) {
|
|
io.GetIoErrorHandler().Crash(
|
|
"SetEncoding() called when not in an OPEN statement");
|
|
}
|
|
bool isUTF8{false};
|
|
static const char *keywords[]{"UTF-8", "DEFAULT", nullptr};
|
|
switch (IdentifyValue(keyword, length, keywords)) {
|
|
case 0:
|
|
isUTF8 = true;
|
|
break;
|
|
case 1:
|
|
isUTF8 = false;
|
|
break;
|
|
default:
|
|
open->SignalError(IostatErrorInKeyword, "Invalid ENCODING='%.*s'",
|
|
static_cast<int>(length), keyword);
|
|
}
|
|
if (isUTF8 != open->unit().isUTF8) {
|
|
if (open->wasExtant()) {
|
|
open->SignalError("ENCODING= may not be changed on an open unit");
|
|
}
|
|
open->unit().isUTF8 = isUTF8;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IONAME(SetForm)(Cookie cookie, const char *keyword, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
auto *open{io.get_if<OpenStatementState>()};
|
|
if (!open) {
|
|
io.GetIoErrorHandler().Crash(
|
|
"SetEncoding() called when not in an OPEN statement");
|
|
}
|
|
bool isUnformatted{false};
|
|
static const char *keywords[]{"FORMATTED", "UNFORMATTED", nullptr};
|
|
switch (IdentifyValue(keyword, length, keywords)) {
|
|
case 0:
|
|
isUnformatted = false;
|
|
break;
|
|
case 1:
|
|
isUnformatted = true;
|
|
break;
|
|
default:
|
|
open->SignalError(IostatErrorInKeyword, "Invalid FORM='%.*s'",
|
|
static_cast<int>(length), keyword);
|
|
}
|
|
if (isUnformatted != open->unit().isUnformatted) {
|
|
if (open->wasExtant()) {
|
|
open->SignalError("FORM= may not be changed on an open unit");
|
|
}
|
|
open->unit().isUnformatted = isUnformatted;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IONAME(SetPosition)(
|
|
Cookie cookie, const char *keyword, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
auto *open{io.get_if<OpenStatementState>()};
|
|
if (!open) {
|
|
io.GetIoErrorHandler().Crash(
|
|
"SetPosition() called when not in an OPEN statement");
|
|
}
|
|
static const char *positions[]{"ASIS", "REWIND", "APPEND", nullptr};
|
|
switch (IdentifyValue(keyword, length, positions)) {
|
|
case 0:
|
|
open->set_position(Position::AsIs);
|
|
return true;
|
|
case 1:
|
|
open->set_position(Position::Rewind);
|
|
return true;
|
|
case 2:
|
|
open->set_position(Position::Append);
|
|
return true;
|
|
default:
|
|
io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
|
|
"Invalid POSITION='%.*s'", static_cast<int>(length), keyword);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IONAME(SetRecl)(Cookie cookie, std::size_t n) {
|
|
IoStatementState &io{*cookie};
|
|
auto *open{io.get_if<OpenStatementState>()};
|
|
if (!open) {
|
|
io.GetIoErrorHandler().Crash(
|
|
"SetRecl() called when not in an OPEN statement");
|
|
}
|
|
if (n <= 0) {
|
|
io.GetIoErrorHandler().SignalError("RECL= must be greater than zero");
|
|
}
|
|
if (open->wasExtant() && open->unit().recordLength.has_value() &&
|
|
*open->unit().recordLength != static_cast<std::int64_t>(n)) {
|
|
open->SignalError("RECL= may not be changed for an open unit");
|
|
}
|
|
open->unit().recordLength = n;
|
|
return true;
|
|
}
|
|
|
|
bool IONAME(SetStatus)(Cookie cookie, const char *keyword, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
if (auto *open{io.get_if<OpenStatementState>()}) {
|
|
static const char *statuses[]{
|
|
"OLD", "NEW", "SCRATCH", "REPLACE", "UNKNOWN", nullptr};
|
|
switch (IdentifyValue(keyword, length, statuses)) {
|
|
case 0:
|
|
open->set_status(OpenStatus::Old);
|
|
return true;
|
|
case 1:
|
|
open->set_status(OpenStatus::New);
|
|
return true;
|
|
case 2:
|
|
open->set_status(OpenStatus::Scratch);
|
|
return true;
|
|
case 3:
|
|
open->set_status(OpenStatus::Replace);
|
|
return true;
|
|
case 4:
|
|
open->set_status(OpenStatus::Unknown);
|
|
return true;
|
|
default:
|
|
io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
|
|
"Invalid STATUS='%.*s'", static_cast<int>(length), keyword);
|
|
}
|
|
return false;
|
|
}
|
|
if (auto *close{io.get_if<CloseStatementState>()}) {
|
|
static const char *statuses[]{"KEEP", "DELETE", nullptr};
|
|
switch (IdentifyValue(keyword, length, statuses)) {
|
|
case 0:
|
|
close->set_status(CloseStatus::Keep);
|
|
return true;
|
|
case 1:
|
|
close->set_status(CloseStatus::Delete);
|
|
return true;
|
|
default:
|
|
io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
|
|
"Invalid STATUS='%.*s'", static_cast<int>(length), keyword);
|
|
}
|
|
return false;
|
|
}
|
|
if (io.get_if<NoopCloseStatementState>()) {
|
|
return true; // don't bother validating STATUS= in a no-op CLOSE
|
|
}
|
|
io.GetIoErrorHandler().Crash(
|
|
"SetStatus() called when not in an OPEN or CLOSE statement");
|
|
}
|
|
|
|
bool IONAME(SetFile)(
|
|
Cookie cookie, const char *path, std::size_t chars, int kind) {
|
|
IoStatementState &io{*cookie};
|
|
if (auto *open{io.get_if<OpenStatementState>()}) {
|
|
open->set_path(path, chars, kind);
|
|
return true;
|
|
}
|
|
io.GetIoErrorHandler().Crash(
|
|
"SetFile() called when not in an OPEN statement");
|
|
return false;
|
|
}
|
|
|
|
static bool SetInteger(int &x, int kind, int value) {
|
|
switch (kind) {
|
|
case 1:
|
|
reinterpret_cast<std::int8_t &>(x) = value;
|
|
return true;
|
|
case 2:
|
|
reinterpret_cast<std::int16_t &>(x) = value;
|
|
return true;
|
|
case 4:
|
|
x = value;
|
|
return true;
|
|
case 8:
|
|
reinterpret_cast<std::int64_t &>(x) = value;
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IONAME(GetNewUnit)(Cookie cookie, int &unit, int kind) {
|
|
IoStatementState &io{*cookie};
|
|
auto *open{io.get_if<OpenStatementState>()};
|
|
if (!open) {
|
|
io.GetIoErrorHandler().Crash(
|
|
"GetNewUnit() called when not in an OPEN statement");
|
|
}
|
|
if (!SetInteger(unit, kind, open->unit().unitNumber())) {
|
|
open->SignalError("GetNewUnit(): Bad INTEGER kind(%d) for result");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Data transfers
|
|
|
|
bool IONAME(OutputDescriptor)(Cookie cookie, const Descriptor &) {
|
|
IoStatementState &io{*cookie};
|
|
io.GetIoErrorHandler().Crash("OutputDescriptor: not yet implemented"); // TODO
|
|
}
|
|
|
|
bool IONAME(OutputUnformattedBlock)(
|
|
Cookie cookie, const char *x, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
if (auto *unf{io.get_if<UnformattedIoStatementState<Direction::Output>>()}) {
|
|
return unf->Emit(x, length);
|
|
}
|
|
io.GetIoErrorHandler().Crash("OutputUnformatted() called for an I/O "
|
|
"statement that is not unformatted output");
|
|
return false;
|
|
}
|
|
|
|
bool IONAME(OutputInteger64)(Cookie cookie, std::int64_t n) {
|
|
IoStatementState &io{*cookie};
|
|
if (!io.get_if<OutputStatementState>()) {
|
|
io.GetIoErrorHandler().Crash(
|
|
"OutputInteger64() called for a non-output I/O statement");
|
|
return false;
|
|
}
|
|
if (auto edit{io.GetNextDataEdit()}) {
|
|
return EditIntegerOutput(io, *edit, n);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IONAME(InputInteger)(Cookie cookie, std::int64_t &n, int kind) {
|
|
IoStatementState &io{*cookie};
|
|
if (!io.get_if<InputStatementState>()) {
|
|
io.GetIoErrorHandler().Crash(
|
|
"InputInteger64() called for a non-input I/O statement");
|
|
return false;
|
|
}
|
|
if (auto edit{io.GetNextDataEdit()}) {
|
|
if (edit->descriptor == DataEdit::ListDirectedNullValue) {
|
|
return true;
|
|
}
|
|
return EditIntegerInput(io, *edit, reinterpret_cast<void *>(&n), kind);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IONAME(OutputReal64)(Cookie cookie, double x) {
|
|
IoStatementState &io{*cookie};
|
|
if (!io.get_if<OutputStatementState>()) {
|
|
io.GetIoErrorHandler().Crash(
|
|
"OutputReal64() called for a non-output I/O statement");
|
|
return false;
|
|
}
|
|
if (auto edit{io.GetNextDataEdit()}) {
|
|
return RealOutputEditing<53>{io, x}.Edit(*edit);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IONAME(InputReal64)(Cookie cookie, double &x) {
|
|
IoStatementState &io{*cookie};
|
|
if (!io.get_if<InputStatementState>()) {
|
|
io.GetIoErrorHandler().Crash(
|
|
"InputReal64() called for a non-input I/O statement");
|
|
return false;
|
|
}
|
|
if (auto edit{io.GetNextDataEdit()}) {
|
|
if (edit->descriptor == DataEdit::ListDirectedNullValue) {
|
|
return true;
|
|
}
|
|
return EditRealInput<53>(io, *edit, reinterpret_cast<void *>(&x));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IONAME(OutputComplex64)(Cookie cookie, double r, double z) {
|
|
IoStatementState &io{*cookie};
|
|
if (io.get_if<ListDirectedStatementState<Direction::Output>>()) {
|
|
DataEdit real, imaginary;
|
|
real.descriptor = DataEdit::ListDirectedRealPart;
|
|
imaginary.descriptor = DataEdit::ListDirectedImaginaryPart;
|
|
return RealOutputEditing<53>{io, r}.Edit(real) &&
|
|
RealOutputEditing<53>{io, z}.Edit(imaginary);
|
|
}
|
|
return IONAME(OutputReal64)(cookie, r) && IONAME(OutputReal64)(cookie, z);
|
|
}
|
|
|
|
bool IONAME(OutputAscii)(Cookie cookie, const char *x, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
if (!io.get_if<OutputStatementState>()) {
|
|
io.GetIoErrorHandler().Crash(
|
|
"OutputAscii() called for a non-output I/O statement");
|
|
return false;
|
|
}
|
|
if (auto *list{io.get_if<ListDirectedStatementState<Direction::Output>>()}) {
|
|
return ListDirectedDefaultCharacterOutput(io, *list, x, length);
|
|
} else if (auto edit{io.GetNextDataEdit()}) {
|
|
return EditDefaultCharacterOutput(io, *edit, x, length);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IONAME(InputAscii)(Cookie cookie, char *x, std::size_t length) {
|
|
IoStatementState &io{*cookie};
|
|
if (!io.get_if<InputStatementState>()) {
|
|
io.GetIoErrorHandler().Crash(
|
|
"InputAscii() called for a non-input I/O statement");
|
|
return false;
|
|
}
|
|
if (auto edit{io.GetNextDataEdit()}) {
|
|
if (edit->descriptor == DataEdit::ListDirectedNullValue) {
|
|
return true;
|
|
}
|
|
return EditDefaultCharacterInput(io, *edit, x, length);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IONAME(OutputLogical)(Cookie cookie, bool truth) {
|
|
IoStatementState &io{*cookie};
|
|
if (!io.get_if<OutputStatementState>()) {
|
|
io.GetIoErrorHandler().Crash(
|
|
"OutputLogical() called for a non-output I/O statement");
|
|
return false;
|
|
}
|
|
if (auto *list{io.get_if<ListDirectedStatementState<Direction::Output>>()}) {
|
|
return ListDirectedLogicalOutput(io, *list, truth);
|
|
} else if (auto edit{io.GetNextDataEdit()}) {
|
|
return EditLogicalOutput(io, *edit, truth);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IONAME(InputLogical)(Cookie cookie, bool &truth) {
|
|
IoStatementState &io{*cookie};
|
|
if (!io.get_if<InputStatementState>()) {
|
|
io.GetIoErrorHandler().Crash(
|
|
"InputLogical() called for a non-input I/O statement");
|
|
return false;
|
|
}
|
|
if (auto edit{io.GetNextDataEdit()}) {
|
|
if (edit->descriptor == DataEdit::ListDirectedNullValue) {
|
|
return true;
|
|
}
|
|
return EditLogicalInput(io, *edit, truth);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void IONAME(GetIoMsg)(Cookie cookie, char *msg, std::size_t length) {
|
|
IoErrorHandler &handler{cookie->GetIoErrorHandler()};
|
|
if (handler.GetIoStat()) { // leave "msg" alone when no error
|
|
handler.GetIoMsg(msg, length);
|
|
}
|
|
}
|
|
|
|
enum Iostat IONAME(EndIoStatement)(Cookie cookie) {
|
|
IoStatementState &io{*cookie};
|
|
return static_cast<enum Iostat>(io.EndIoStatement());
|
|
}
|
|
} // namespace Fortran::runtime::io
|