[flang] Implement runtime support for INQUIRE statements

Differential Revision: https://reviews.llvm.org/D85166
This commit is contained in:
peter klausler 2020-08-03 11:35:29 -07:00
parent ffe0066b62
commit 675ad1bc6a
12 changed files with 760 additions and 75 deletions

View File

@ -23,6 +23,23 @@
namespace Fortran::runtime::io {
const char *InquiryKeywordHashDecode(
char *buffer, std::size_t n, InquiryKeywordHash hash) {
if (n < 1) {
return nullptr;
}
char *p{buffer + n};
*--p = '\0';
while (hash > 1) {
if (p < buffer) {
return nullptr;
}
*--p = 'A' + (hash % 26);
hash /= 26;
}
return hash == 1 ? p : nullptr;
}
template <Direction DIR>
Cookie BeginInternalArrayListIO(const Descriptor &descriptor,
void ** /*scratchArea*/, std::size_t /*scratchBytes*/,
@ -289,8 +306,8 @@ Cookie IONAME(BeginBackspace)(
Cookie IONAME(BeginEndfile)(
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
Terminator terminator{sourceFile, sourceLine};
ExternalFileUnit &unit{
ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)};
ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous(
unitNumber, Direction::Output, true /*formatted*/, terminator)};
return &unit.BeginIoStatement<ExternalMiscIoStatementState>(
unit, ExternalMiscIoStatementState::Endfile, sourceFile, sourceLine);
}
@ -298,12 +315,50 @@ Cookie IONAME(BeginEndfile)(
Cookie IONAME(BeginRewind)(
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
Terminator terminator{sourceFile, sourceLine};
ExternalFileUnit &unit{
ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)};
ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous(
unitNumber, Direction::Input, true /*formatted*/, terminator)};
return &unit.BeginIoStatement<ExternalMiscIoStatementState>(
unit, ExternalMiscIoStatementState::Rewind, sourceFile, sourceLine);
}
Cookie IONAME(BeginInquireUnit)(
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) {
return &unit->BeginIoStatement<InquireUnitState>(
*unit, sourceFile, sourceLine);
} else {
// INQUIRE(UNIT=unrecognized unit)
Terminator oom{sourceFile, sourceLine};
return &New<InquireNoUnitState>{oom}(sourceFile, sourceLine)
.release()
->ioStatementState();
}
}
Cookie IONAME(BeginInquireFile)(const char *path, std::size_t pathLength,
const char *sourceFile, int sourceLine) {
Terminator oom{sourceFile, sourceLine};
auto trimmed{
SaveDefaultCharacter(path, TrimTrailingSpaces(path, pathLength), oom)};
if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(trimmed.get())}) {
// INQUIRE(FILE=) to a connected unit
return &unit->BeginIoStatement<InquireUnitState>(
*unit, sourceFile, sourceLine);
} else {
return &New<InquireUnconnectedFileState>{oom}(
std::move(trimmed), sourceFile, sourceLine)
.release()
->ioStatementState();
}
}
Cookie IONAME(BeginInquireIoLength)(const char *sourceFile, int sourceLine) {
Terminator oom{sourceFile, sourceLine};
return &New<InquireIOLengthState>{oom}(sourceFile, sourceLine)
.release()
->ioStatementState();
}
// Control list items
void IONAME(EnableHandlers)(Cookie cookie, bool hasIoStat, bool hasErr,
@ -522,29 +577,21 @@ bool IONAME(SetAccess)(Cookie cookie, const char *keyword, std::size_t length) {
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;
open->set_access(Access::Sequential);
break;
case 1:
access = Access::Direct;
open->set_access(Access::Direct);
break;
case 2:
access = Access::Stream;
open->set_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;
}
@ -661,25 +708,18 @@ bool IONAME(SetForm)(Cookie cookie, const char *keyword, std::size_t length) {
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;
open->set_isUnformatted(false);
break;
case 1:
isUnformatted = true;
open->set_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;
}
@ -777,11 +817,10 @@ bool IONAME(SetStatus)(Cookie cookie, const char *keyword, std::size_t length) {
"SetStatus() called when not in an OPEN or CLOSE statement");
}
bool IONAME(SetFile)(
Cookie cookie, const char *path, std::size_t chars, int kind) {
bool IONAME(SetFile)(Cookie cookie, const char *path, std::size_t chars) {
IoStatementState &io{*cookie};
if (auto *open{io.get_if<OpenStatementState>()}) {
open->set_path(path, chars, kind);
open->set_path(path, chars);
return true;
}
io.GetIoErrorHandler().Crash(
@ -789,7 +828,8 @@ bool IONAME(SetFile)(
return false;
}
static bool SetInteger(int &x, int kind, int value) {
template <typename INT>
static bool SetInteger(INT &x, int kind, std::int64_t value) {
switch (kind) {
case 1:
reinterpret_cast<std::int8_t &>(x) = value;
@ -798,7 +838,7 @@ static bool SetInteger(int &x, int kind, int value) {
reinterpret_cast<std::int16_t &>(x) = value;
return true;
case 4:
x = value;
reinterpret_cast<std::int32_t &>(x) = value;
return true;
case 8:
reinterpret_cast<std::int64_t &>(x) = value;
@ -1059,6 +1099,34 @@ void IONAME(GetIoMsg)(Cookie cookie, char *msg, std::size_t length) {
}
}
bool IONAME(InquireCharacter)(Cookie cookie, InquiryKeywordHash inquiry,
char *result, std::size_t length) {
IoStatementState &io{*cookie};
return io.Inquire(inquiry, result, length);
}
bool IONAME(InquireLogical)(
Cookie cookie, InquiryKeywordHash inquiry, bool &result) {
IoStatementState &io{*cookie};
return io.Inquire(inquiry, result);
}
bool IONAME(InquirePendingId)(Cookie cookie, std::int64_t id, bool &result) {
IoStatementState &io{*cookie};
return io.Inquire(HashInquiryKeyword("PENDING"), id, result);
}
bool IONAME(InquireInteger64)(
Cookie cookie, InquiryKeywordHash inquiry, std::int64_t &result, int kind) {
IoStatementState &io{*cookie};
std::int64_t n;
if (io.Inquire(inquiry, n)) {
SetInteger(result, kind, n);
return true;
}
return false;
}
enum Iostat IONAME(EndIoStatement)(Cookie cookie) {
IoStatementState &io{*cookie};
return static_cast<enum Iostat>(io.EndIoStatement());

View File

@ -29,6 +29,26 @@ using ExternalUnit = int;
using AsynchronousId = int;
static constexpr ExternalUnit DefaultUnit{-1}; // READ(*), WRITE(*), PRINT
// INQUIRE specifiers are encoded as simple base-26 packings of
// the spellings of their keywords.
using InquiryKeywordHash = std::uint64_t;
constexpr InquiryKeywordHash HashInquiryKeyword(const char *p) {
InquiryKeywordHash hash{1};
while (char ch{*p++}) {
std::uint64_t letter{0};
if (ch >= 'a' && ch <= 'z') {
letter = ch - 'a';
} else {
letter = ch - 'A';
}
hash = 26 * hash + letter;
}
return hash;
}
const char *InquiryKeywordHashDecode(
char *buffer, std::size_t, InquiryKeywordHash);
extern "C" {
#define IONAME(name) RTNAME(io##name)
@ -150,7 +170,7 @@ Cookie IONAME(BeginOpenNewUnit)(
// BeginInquireIoLength() is basically a no-op output statement.
Cookie IONAME(BeginInquireUnit)(
ExternalUnit, const char *sourceFile = nullptr, int sourceLine = 0);
Cookie IONAME(BeginInquireFile)(const char *, std::size_t, int kind = 1,
Cookie IONAME(BeginInquireFile)(const char *, std::size_t,
const char *sourceFile = nullptr, int sourceLine = 0);
Cookie IONAME(BeginInquireIoLength)(
const char *sourceFile = nullptr, int sourceLine = 0);
@ -255,10 +275,7 @@ bool IONAME(SetRecl)(Cookie, std::size_t); // RECL=
// For CLOSE: STATUS=KEEP, DELETE
bool IONAME(SetStatus)(Cookie, const char *, std::size_t);
// SetFile() may pass a CHARACTER argument of non-default kind,
// and such filenames are converted to UTF-8 before being
// presented to the filesystem.
bool IONAME(SetFile)(Cookie, const char *, std::size_t chars, int kind = 1);
bool IONAME(SetFile)(Cookie, const char *, std::size_t chars);
// Acquires the runtime-created unit number for OPEN(NEWUNIT=)
bool IONAME(GetNewUnit)(Cookie, int &, int kind = 4);
@ -275,18 +292,17 @@ void IONAME(GetIoMsg)(Cookie, char *, std::size_t); // IOMSG=
// INQUIRE() specifiers are mostly identified by their NUL-terminated
// case-insensitive names.
// ACCESS, ACTION, ASYNCHRONOUS, BLANK, DECIMAL, DELIM, DIRECT, ENCODING,
// FORM, FORMATTED, NAME, PAD, POSITION, READ, READWRITE, ROUND,
// ACCESS, ACTION, ASYNCHRONOUS, BLANK, CONVERT, DECIMAL, DELIM, DIRECT,
// ENCODING, FORM, FORMATTED, NAME, PAD, POSITION, READ, READWRITE, ROUND,
// SEQUENTIAL, SIGN, STREAM, UNFORMATTED, WRITE:
bool IONAME(InquireCharacter)(
Cookie, const char *specifier, char *, std::size_t);
bool IONAME(InquireCharacter)(Cookie, InquiryKeywordHash, char *, std::size_t);
// EXIST, NAMED, OPENED, and PENDING (without ID):
bool IONAME(InquireLogical)(Cookie, const char *specifier, bool &);
bool IONAME(InquireLogical)(Cookie, InquiryKeywordHash, bool &);
// PENDING with ID
bool IONAME(InquirePendingId)(Cookie, std::int64_t, bool &);
// NEXTREC, NUMBER, POS, RECL, SIZE
bool IONAME(InquireInteger64)(
Cookie, const char *specifier, std::int64_t &, int kind = 8);
Cookie, InquiryKeywordHash, std::int64_t &, int kind = 8);
// This function must be called to end an I/O statement, and its
// cookie value may not be used afterwards unless it is recycled

View File

@ -38,7 +38,7 @@ public:
void SignalError(int iostatOrErrno, const char *msg, ...);
void SignalError(int iostatOrErrno);
template <typename... X> void SignalError(const char *msg, X &&... xs) {
template <typename... X> void SignalError(const char *msg, X &&...xs) {
SignalError(IostatGenericError, msg, std::forward<X>(xs)...);
}

View File

@ -26,6 +26,37 @@ std::optional<DataEdit> IoStatementBase::GetNextDataEdit(
return std::nullopt;
}
bool IoStatementBase::Inquire(InquiryKeywordHash, char *, std::size_t) {
Crash(
"IoStatementBase::Inquire() called for I/O statement other than INQUIRE");
return false;
}
bool IoStatementBase::Inquire(InquiryKeywordHash, bool &) {
Crash(
"IoStatementBase::Inquire() called for I/O statement other than INQUIRE");
return false;
}
bool IoStatementBase::Inquire(InquiryKeywordHash, std::int64_t, bool &) {
Crash(
"IoStatementBase::Inquire() called for I/O statement other than INQUIRE");
return false;
}
bool IoStatementBase::Inquire(InquiryKeywordHash, std::int64_t &) {
Crash(
"IoStatementBase::Inquire() called for I/O statement other than INQUIRE");
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, typename CHAR>
InternalIoStatementState<DIR, CHAR>::InternalIoStatementState(
Buffer scalar, std::size_t length, const char *sourceFile, int sourceLine)
@ -151,14 +182,9 @@ int ExternalIoStatementBase::EndIoStatement() {
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;
void OpenStatementState::set_path(const char *path, std::size_t length) {
pathLength_ = TrimTrailingSpaces(path, length);
path_ = SaveDefaultCharacter(path, pathLength_, *this);
}
int OpenStatementState::EndIoStatement() {
@ -166,8 +192,31 @@ int OpenStatementState::EndIoStatement() {
SignalError("OPEN statement for connected unit may not have STATUS= other "
"than 'OLD'");
}
unit().OpenUnit(status_.value_or(OpenStatus::Unknown), action_, position_,
std::move(path_), pathLength_, convert_, *this);
if (path_.get() || wasExtant_ ||
(status_ && *status_ == OpenStatus::Scratch)) {
unit().OpenUnit(status_.value_or(OpenStatus::Unknown), action_, position_,
std::move(path_), pathLength_, convert_, *this);
} else {
unit().OpenAnonymousUnit(status_.value_or(OpenStatus::Unknown), action_,
position_, convert_, *this);
}
if (access_) {
if (*access_ != unit().access) {
if (wasExtant_) {
SignalError("ACCESS= may not be changed on an open unit");
}
}
unit().access = *access_;
}
if (!isUnformatted_) {
isUnformatted_ = unit().access != Access::Sequential;
}
if (*isUnformatted_ != unit().isUnformatted) {
if (wasExtant_) {
SignalError("FORM= may not be changed on an open unit");
}
unit().isUnformatted = *isUnformatted_;
}
return ExternalIoStatementBase::EndIoStatement();
}
@ -178,7 +227,7 @@ int CloseStatementState::EndIoStatement() {
return result;
}
int NoopCloseStatementState::EndIoStatement() {
int NoUnitIoStatementState::EndIoStatement() {
auto result{IoStatementBase::EndIoStatement()};
FreeMemory(this);
return result;
@ -454,6 +503,26 @@ bool ListDirectedStatementState<Direction::Output>::NeedAdvance(
width > connection.RemainingSpaceInRecord();
}
bool IoStatementState::Inquire(
InquiryKeywordHash inquiry, char *out, std::size_t chars) {
return std::visit(
[&](auto &x) { return x.get().Inquire(inquiry, out, chars); }, u_);
}
bool IoStatementState::Inquire(InquiryKeywordHash inquiry, bool &out) {
return std::visit([&](auto &x) { return x.get().Inquire(inquiry, out); }, u_);
}
bool IoStatementState::Inquire(
InquiryKeywordHash inquiry, std::int64_t id, bool &out) {
return std::visit(
[&](auto &x) { return x.get().Inquire(inquiry, id, out); }, u_);
}
bool IoStatementState::Inquire(InquiryKeywordHash inquiry, std::int64_t &n) {
return std::visit([&](auto &x) { return x.get().Inquire(inquiry, n); }, u_);
}
bool ListDirectedStatementState<Direction::Output>::EmitLeadingSpaceOrAdvance(
IoStatementState &io, std::size_t length, bool isCharacter) {
if (length == 0) {
@ -678,4 +747,419 @@ int ExternalMiscIoStatementState::EndIoStatement() {
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) {
const char *str{nullptr};
switch (inquiry) {
case HashInquiryKeyword("ACCESS"):
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().mayWrite() ? unit().mayRead() ? "READWRITE" : "WRITE" : "READ";
break;
case HashInquiryKeyword("ASYNCHRONOUS"):
str = unit().mayAsynchronous() ? "YES" : "NO";
break;
case HashInquiryKeyword("BLANK"):
str = unit().isUnformatted ? "UNDEFINED"
: unit().modes.editingFlags & blankZero ? "ZERO"
: "NULL";
break;
case HashInquiryKeyword("CONVERT"):
str = unit().swapEndianness() ? "SWAP" : "NATIVE";
break;
case HashInquiryKeyword("DECIMAL"):
str = unit().isUnformatted ? "UNDEFINED"
: unit().modes.editingFlags & decimalComma ? "COMMA"
: "POINT";
break;
case HashInquiryKeyword("DELIM"):
if (unit().isUnformatted) {
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().mayPosition() ? "YES" : "NO";
break;
case HashInquiryKeyword("ENCODING"):
str = unit().isUnformatted ? "UNDEFINED"
: unit().isUTF8 ? "UTF-8"
: "ASCII";
break;
case HashInquiryKeyword("FORM"):
str = unit().isUnformatted ? "UNFORMATTED" : "FORMATTED";
break;
case HashInquiryKeyword("FORMATTED"):
str = "YES";
break;
case HashInquiryKeyword("NAME"):
str = unit().path();
if (!str) {
return true; // result is undefined
}
break;
case HashInquiryKeyword("PAD"):
str = unit().isUnformatted ? "UNDEFINED" : unit().modes.pad ? "YES" : "NO";
break;
case HashInquiryKeyword("POSITION"):
if (unit().access == Access::Direct) {
str = "UNDEFINED";
} else {
auto size{unit().knownSize()};
auto pos{unit().position()};
if (pos == size.value_or(pos + 1)) {
str = "APPEND";
} else if (pos == 0) {
str = "REWIND";
} else {
str = "ASIS"; // processor-dependent & no common behavior
}
}
break;
case HashInquiryKeyword("READ"):
str = unit().mayRead() ? "YES" : "NO";
break;
case HashInquiryKeyword("READWRITE"):
str = unit().mayRead() && unit().mayWrite() ? "YES" : "NO";
break;
case HashInquiryKeyword("ROUND"):
if (unit().isUnformatted) {
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"):
str = "YES";
break;
case HashInquiryKeyword("SIGN"):
str = unit().isUnformatted ? "UNDEFINED"
: unit().modes.editingFlags & signPlus ? "PLUS"
: "SUPPRESS";
break;
case HashInquiryKeyword("STREAM"):
str = "YES";
break;
case HashInquiryKeyword("WRITE"):
str = unit().mayWrite() ? "YES" : "NO";
break;
case HashInquiryKeyword("UNFORMATTED"):
str = "YES";
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 = true;
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().position();
return true;
case HashInquiryKeyword("RECL"):
if (unit().access == Access::Stream) {
result = -2;
} else if (unit().isFixedRecordLength && unit().recordLength) {
result = *unit().recordLength;
} else {
result = std::numeric_limits<std::uint32_t>::max();
}
return true;
case HashInquiryKeyword("SIZE"):
if (auto size{unit().knownSize()}) {
result = *size;
} else {
result = -1;
}
return true;
default:
BadInquiryKeywordHashCrash(inquiry);
return false;
}
}
InquireNoUnitState::InquireNoUnitState(const char *sourceFile, int sourceLine)
: NoUnitIoStatementState{sourceFile, sourceLine, *this} {}
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("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, "UNKNONN");
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("NEXTREC"):
case HashInquiryKeyword("NUMBER"):
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{sourceFile, sourceLine, *this}, 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("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"):
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("FORMATTED"):
case HashInquiryKeyword("SEQUENTIAL"):
case HashInquiryKeyword("STREAM"):
case HashInquiryKeyword("UNFORMATTED"):
str = "YES";
break;
case HashInquiryKeyword("NAME"):
str = path_.get();
return true;
}
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"):
case HashInquiryKeyword("SIZE"):
result = -1;
return true;
default:
BadInquiryKeywordHashCrash(inquiry);
return false;
}
}
InquireIOLengthState::InquireIOLengthState(
const char *sourceFile, int sourceLine)
: NoUnitIoStatementState{sourceFile, sourceLine, *this} {}
bool InquireIOLengthState::Emit(
const char *, std::size_t n, std::size_t /*elementBytes*/) {
bytes_ += n;
return true;
}
} // namespace Fortran::runtime::io

View File

@ -16,6 +16,7 @@
#include "file.h"
#include "format.h"
#include "internal-unit.h"
#include "io-api.h"
#include "io-error.h"
#include <functional>
#include <type_traits>
@ -26,6 +27,11 @@ namespace Fortran::runtime::io {
class ExternalFileUnit;
class OpenStatementState;
class InquireUnitState;
class InquireNoUnitState;
class InquireUnconnectedFileState;
class InquireIOLengthState;
class ExternalMiscIoStatementState;
class CloseStatementState;
class NoopCloseStatementState;
@ -36,7 +42,6 @@ template <Direction, typename CHAR = char>
class ExternalFormattedIoStatementState;
template <Direction> class ExternalListIoStatementState;
template <Direction> class UnformattedIoStatementState;
class ExternalMiscIoStatementState;
// The Cookie type in the I/O API is a pointer (for C) to this class.
class IoStatementState {
@ -60,6 +65,10 @@ public:
ExternalFileUnit *GetExternalFileUnit() const; // null if internal unit
MutableModes &mutableModes();
void BeginReadingRecord();
bool Inquire(InquiryKeywordHash, char *, std::size_t);
bool Inquire(InquiryKeywordHash, bool &);
bool Inquire(InquiryKeywordHash, std::int64_t, bool &); // PENDING=
bool Inquire(InquiryKeywordHash, std::int64_t &);
// N.B.: this also works with base classes
template <typename A> A *get_if() const {
@ -98,6 +107,10 @@ private:
std::reference_wrapper<ExternalListIoStatementState<Direction::Input>>,
std::reference_wrapper<UnformattedIoStatementState<Direction::Output>>,
std::reference_wrapper<UnformattedIoStatementState<Direction::Input>>,
std::reference_wrapper<InquireUnitState>,
std::reference_wrapper<InquireNoUnitState>,
std::reference_wrapper<InquireUnconnectedFileState>,
std::reference_wrapper<InquireIOLengthState>,
std::reference_wrapper<ExternalMiscIoStatementState>>
u_;
};
@ -110,6 +123,12 @@ struct IoStatementBase : public DefaultFormatControlCallbacks {
std::optional<DataEdit> GetNextDataEdit(IoStatementState &, int = 1);
ExternalFileUnit *GetExternalFileUnit() const { return nullptr; }
void BeginReadingRecord() {}
bool Inquire(InquiryKeywordHash, char *, std::size_t);
bool Inquire(InquiryKeywordHash, bool &);
bool Inquire(InquiryKeywordHash, std::int64_t, bool &);
bool Inquire(InquiryKeywordHash, std::int64_t &);
void BadInquiryKeywordHashCrash(InquiryKeywordHash);
};
struct InputStatementState {};
@ -303,10 +322,12 @@ public:
wasExtant} {}
bool wasExtant() const { return wasExtant_; }
void set_status(OpenStatus status) { status_ = status; } // STATUS=
void set_path(const char *, std::size_t, int kind); // FILE=
void set_path(const char *, std::size_t); // FILE=
void set_position(Position position) { position_ = position; } // POSITION=
void set_action(Action action) { action_ = action; } // ACTION=
void set_convert(Convert convert) { convert_ = convert; } // CONVERT=
void set_access(Access access) { access_ = access; } // ACCESS=
void set_isUnformatted(bool yes = true) { isUnformatted_ = yes; } // FORM=
int EndIoStatement();
private:
@ -317,6 +338,8 @@ private:
Convert convert_{Convert::Native};
OwningPtr<char> path_;
std::size_t pathLength_;
std::optional<bool> isUnformatted_;
std::optional<Access> access_;
};
class CloseStatementState : public ExternalIoStatementBase {
@ -331,21 +354,31 @@ private:
CloseStatus status_{CloseStatus::Keep};
};
class NoopCloseStatementState : public IoStatementBase {
// For CLOSE(bad unit) and INQUIRE(unconnected unit)
class NoUnitIoStatementState : public IoStatementBase {
public:
NoopCloseStatementState(const char *sourceFile, int sourceLine)
: IoStatementBase{sourceFile, sourceLine}, ioStatementState_{*this} {}
IoStatementState &ioStatementState() { return ioStatementState_; }
void set_status(CloseStatus) {} // discards
MutableModes &mutableModes() { return connection_.modes; }
ConnectionState &GetConnectionState() { return connection_; }
int EndIoStatement();
protected:
template <typename A>
NoUnitIoStatementState(const char *sourceFile, int sourceLine, A &stmt)
: IoStatementBase{sourceFile, sourceLine}, ioStatementState_{stmt} {}
private:
IoStatementState ioStatementState_; // points to *this
ConnectionState connection_;
};
class NoopCloseStatementState : public NoUnitIoStatementState {
public:
NoopCloseStatementState(const char *sourceFile, int sourceLine)
: NoUnitIoStatementState{sourceFile, sourceLine, *this} {}
void set_status(CloseStatus) {} // discards
};
extern template class InternalIoStatementState<Direction::Output>;
extern template class InternalIoStatementState<Direction::Input>;
extern template class InternalFormattedIoStatementState<Direction::Output>;
@ -369,6 +402,49 @@ extern template class FormatControl<
extern template class FormatControl<
ExternalFormattedIoStatementState<Direction::Input>>;
class InquireUnitState : public ExternalIoStatementBase {
public:
InquireUnitState(ExternalFileUnit &unit, const char *sourceFile = nullptr,
int sourceLine = 0);
bool Inquire(InquiryKeywordHash, char *, std::size_t);
bool Inquire(InquiryKeywordHash, bool &);
bool Inquire(InquiryKeywordHash, std::int64_t, bool &);
bool Inquire(InquiryKeywordHash, std::int64_t &);
};
class InquireNoUnitState : public NoUnitIoStatementState {
public:
InquireNoUnitState(const char *sourceFile = nullptr, int sourceLine = 0);
bool Inquire(InquiryKeywordHash, char *, std::size_t);
bool Inquire(InquiryKeywordHash, bool &);
bool Inquire(InquiryKeywordHash, std::int64_t, bool &);
bool Inquire(InquiryKeywordHash, std::int64_t &);
};
class InquireUnconnectedFileState : public NoUnitIoStatementState {
public:
InquireUnconnectedFileState(OwningPtr<char> &&path,
const char *sourceFile = nullptr, int sourceLine = 0);
bool Inquire(InquiryKeywordHash, char *, std::size_t);
bool Inquire(InquiryKeywordHash, bool &);
bool Inquire(InquiryKeywordHash, std::int64_t, bool &);
bool Inquire(InquiryKeywordHash, std::int64_t &);
private:
OwningPtr<char> path_; // trimmed and NUL terminated
};
class InquireIOLengthState : public NoUnitIoStatementState,
public OutputStatementState {
public:
InquireIOLengthState(const char *sourceFile = nullptr, int sourceLine = 0);
std::size_t bytes() const { return bytes_; }
bool Emit(const char *, std::size_t, std::size_t elementBytes = 0);
private:
std::size_t bytes_{0};
};
class ExternalMiscIoStatementState : public ExternalIoStatementBase {
public:
enum Which { Flush, Backspace, Endfile, Rewind };

View File

@ -42,7 +42,7 @@ template <typename A> class SizedNew {
public:
explicit SizedNew(const Terminator &terminator) : terminator_{terminator} {}
template <typename... X>
[[nodiscard]] OwningPtr<A> operator()(std::size_t bytes, X &&... x) {
[[nodiscard]] OwningPtr<A> operator()(std::size_t bytes, X &&...x) {
return OwningPtr<A>{new (AllocateMemoryOrCrash(terminator_, bytes))
A{std::forward<X>(x)...}};
}
@ -53,7 +53,7 @@ private:
template <typename A> struct New : public SizedNew<A> {
using SizedNew<A>::SizedNew;
template <typename... X> [[nodiscard]] OwningPtr<A> operator()(X &&... x) {
template <typename... X> [[nodiscard]] OwningPtr<A> operator()(X &&...x) {
return SizedNew<A>::operator()(sizeof(A), std::forward<X>(x)...);
}
};

View File

@ -12,6 +12,13 @@
namespace Fortran::runtime {
std::size_t TrimTrailingSpaces(const char *s, std::size_t n) {
while (n > 0 && s[n - 1] == ' ') {
--n;
}
return n;
}
OwningPtr<char> SaveDefaultCharacter(
const char *s, std::size_t length, const Terminator &terminator) {
if (s) {

View File

@ -18,6 +18,8 @@ namespace Fortran::runtime {
class Terminator;
std::size_t TrimTrailingSpaces(const char *, std::size_t);
OwningPtr<char> SaveDefaultCharacter(
const char *, std::size_t, const Terminator &);

View File

@ -72,6 +72,20 @@ void UnitMap::FlushAll(IoErrorHandler &handler) {
}
}
ExternalFileUnit *UnitMap::Find(const char *path) {
if (path) {
// TODO: Faster data structure
for (int j{0}; j < buckets_; ++j) {
for (Chain *p{bucket_[j].get()}; p; p = p->next.get()) {
if (p->unit.path() && std::strcmp(p->unit.path(), path) == 0) {
return &p->unit;
}
}
}
}
return nullptr;
}
ExternalFileUnit &UnitMap::Create(int n, const Terminator &terminator) {
Chain &chain{*New<Chain>{terminator}(n).release()};
chain.next.reset(&chain);

View File

@ -34,6 +34,12 @@ public:
return p ? *p : Create(n, terminator);
}
// Unit look-up by name is needed for INQUIRE(FILE="...")
ExternalFileUnit *LookUp(const char *path) {
CriticalSection critical{lock_};
return Find(path);
}
ExternalFileUnit &NewUnit(const Terminator &terminator) {
CriticalSection critical{lock_};
return Create(nextNewUnit_--, terminator);
@ -72,6 +78,7 @@ private:
}
return nullptr;
}
ExternalFileUnit *Find(const char *path);
ExternalFileUnit &Create(int, const Terminator &);

View File

@ -59,20 +59,19 @@ ExternalFileUnit &ExternalFileUnit::LookUpOrCreateAnonymous(
ExternalFileUnit &result{
GetUnitMap().LookUpOrCreate(unit, terminator, exists)};
if (!exists) {
// I/O to an unconnected unit reads/creates a local file, e.g. fort.7
std::size_t pathMaxLen{32};
auto path{SizedNew<char>{terminator}(pathMaxLen)};
std::snprintf(path.get(), pathMaxLen, "fort.%d", unit);
IoErrorHandler handler{terminator};
result.OpenUnit(
dir == Direction::Input ? OpenStatus::Old : OpenStatus::Replace,
Action::ReadWrite, Position::Rewind, std::move(path),
std::strlen(path.get()), Convert::Native, handler);
result.OpenAnonymousUnit(
dir == Direction::Input ? OpenStatus::Unknown : OpenStatus::Replace,
Action::ReadWrite, Position::Rewind, Convert::Native, handler);
result.isUnformatted = isUnformatted;
}
return result;
}
ExternalFileUnit *ExternalFileUnit::LookUp(const char *path) {
return GetUnitMap().LookUp(path);
}
ExternalFileUnit &ExternalFileUnit::CreateNew(
int unit, const Terminator &terminator) {
bool wasExtant{false};
@ -125,10 +124,7 @@ void ExternalFileUnit::OpenUnit(OpenStatus status, std::optional<Action> action,
handler.SignalError(IostatOpenBadRecl,
"OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid",
unitNumber(), static_cast<std::intmax_t>(*recordLength));
} else if (!totalBytes) {
handler.SignalError(IostatOpenUnknownSize,
"OPEN(UNIT=%d,ACCESS='DIRECT'): file size is not known");
} else if (*totalBytes % *recordLength != 0) {
} else if (totalBytes && (*totalBytes % *recordLength != 0)) {
handler.SignalError(IostatOpenBadAppend,
"OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an "
"even divisor of the file size %jd",
@ -137,7 +133,7 @@ void ExternalFileUnit::OpenUnit(OpenStatus status, std::optional<Action> action,
}
}
if (position == Position::Append) {
if (*totalBytes && recordLength && *recordLength) {
if (totalBytes && recordLength && *recordLength) {
endfileRecordNumber = 1 + (*totalBytes / *recordLength);
} else {
// Fake it so that we can backspace relative from the end
@ -149,6 +145,17 @@ void ExternalFileUnit::OpenUnit(OpenStatus status, std::optional<Action> action,
}
}
void ExternalFileUnit::OpenAnonymousUnit(OpenStatus status,
std::optional<Action> action, Position position, Convert convert,
IoErrorHandler &handler) {
// I/O to an unconnected unit reads/creates a local file, e.g. fort.7
std::size_t pathMaxLen{32};
auto path{SizedNew<char>{handler}(pathMaxLen)};
std::snprintf(path.get(), pathMaxLen, "fort.%d", unitNumber_);
OpenUnit(status, action, position, std::move(path), std::strlen(path.get()),
convert, handler);
}
void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) {
DoImpliedEndfile(handler);
Flush(handler);

View File

@ -35,6 +35,7 @@ class ExternalFileUnit : public ConnectionState,
public:
explicit ExternalFileUnit(int unitNumber) : unitNumber_{unitNumber} {}
int unitNumber() const { return unitNumber_; }
bool swapEndianness() const { return swapEndianness_; }
static ExternalFileUnit *LookUp(int unit);
static ExternalFileUnit &LookUpOrCrash(int unit, const Terminator &);
@ -42,6 +43,7 @@ public:
int unit, const Terminator &, bool &wasExtant);
static ExternalFileUnit &LookUpOrCreateAnonymous(
int unit, Direction, bool isUnformatted, const Terminator &);
static ExternalFileUnit *LookUp(const char *path);
static ExternalFileUnit &CreateNew(int unit, const Terminator &);
static ExternalFileUnit *LookUpForClose(int unit);
static int NewUnit(const Terminator &);
@ -51,13 +53,15 @@ public:
void OpenUnit(OpenStatus, std::optional<Action>, Position,
OwningPtr<char> &&path, std::size_t pathLength, Convert,
IoErrorHandler &);
void OpenAnonymousUnit(
OpenStatus, std::optional<Action>, Position, Convert, IoErrorHandler &);
void CloseUnit(CloseStatus, IoErrorHandler &);
void DestroyClosed();
bool SetDirection(Direction, IoErrorHandler &);
template <typename A, typename... X>
IoStatementState &BeginIoStatement(X &&... xs) {
IoStatementState &BeginIoStatement(X &&...xs) {
// TODO: Child data transfer statements vs. locking
lock_.Take(); // dropped in EndIoStatement()
A &state{u_.emplace<A>(std::forward<X>(xs)...)};
@ -111,7 +115,7 @@ private:
ExternalListIoStatementState<Direction::Output>,
ExternalListIoStatementState<Direction::Input>,
UnformattedIoStatementState<Direction::Output>,
UnformattedIoStatementState<Direction::Input>,
UnformattedIoStatementState<Direction::Input>, InquireUnitState,
ExternalMiscIoStatementState>
u_;