forked from OSchip/llvm-project
[flang] Implement READ(SIZE=) and INQUIRE(IOLENGTH=) in runtime
Count input characters corresponding to formatted edit descriptors for READ(SIZE=); count output bytes for INQUIRE(IOLENGTH=). The I/O APIs GetSize() and GetLength() were adjusted to return std::size_t as function results. Basic unit tests were added (and others fixed). Differential Revision: https://reviews.llvm.org/D110291
This commit is contained in:
parent
e6126faba0
commit
4393e3776b
|
@ -280,10 +280,10 @@ bool IONAME(SetFile)(Cookie, const char *, std::size_t chars);
|
|||
bool IONAME(GetNewUnit)(Cookie, int &, int kind = 4);
|
||||
|
||||
// READ(SIZE=), after all input items
|
||||
bool IONAME(GetSize)(Cookie, std::int64_t, int kind = 8);
|
||||
std::size_t IONAME(GetSize)(Cookie);
|
||||
|
||||
// INQUIRE(IOLENGTH=), after all output items
|
||||
bool IONAME(GetIoLength)(Cookie, std::int64_t, int kind = 8);
|
||||
std::size_t IONAME(GetIoLength)(Cookie);
|
||||
|
||||
// GetIoMsg() does not modify its argument unless an error or
|
||||
// end-of-record/file condition is present.
|
||||
|
|
|
@ -315,7 +315,9 @@ static bool UnformattedDescriptorIO(
|
|||
// Regular derived type unformatted I/O, not user-defined
|
||||
auto *externalUnf{io.get_if<ExternalUnformattedIoStatementState<DIR>>()};
|
||||
auto *childUnf{io.get_if<ChildUnformattedIoStatementState<DIR>>()};
|
||||
RUNTIME_CHECK(handler, externalUnf != nullptr || childUnf != nullptr);
|
||||
auto *inq{
|
||||
DIR == Direction::Output ? io.get_if<InquireIOLengthState>() : nullptr};
|
||||
RUNTIME_CHECK(handler, externalUnf || childUnf || inq);
|
||||
std::size_t elementBytes{descriptor.ElementBytes()};
|
||||
std::size_t numElements{descriptor.Elements()};
|
||||
SubscriptValue subscripts[maxRank];
|
||||
|
@ -326,7 +328,8 @@ static bool UnformattedDescriptorIO(
|
|||
std::size_t elementBytes) -> bool {
|
||||
if constexpr (DIR == Direction::Output) {
|
||||
return externalUnf ? externalUnf->Emit(&x, totalBytes, elementBytes)
|
||||
: childUnf->Emit(&x, totalBytes, elementBytes);
|
||||
: childUnf ? childUnf->Emit(&x, totalBytes, elementBytes)
|
||||
: inq->Emit(&x, totalBytes, elementBytes);
|
||||
} else {
|
||||
return externalUnf ? externalUnf->Receive(&x, totalBytes, elementBytes)
|
||||
: childUnf->Receive(&x, totalBytes, elementBytes);
|
||||
|
@ -363,7 +366,7 @@ static bool DescriptorIO(IoStatementState &io, const Descriptor &descriptor) {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if (!io.get_if<FormattedIoStatementState>()) {
|
||||
if (!io.get_if<FormattedIoStatementState<DIR>>()) {
|
||||
return UnformattedDescriptorIO<DIR>(io, descriptor);
|
||||
}
|
||||
IoErrorHandler &handler{io.GetIoErrorHandler()};
|
||||
|
|
|
@ -56,6 +56,7 @@ static bool ScanNumericPrefix(IoStatementState &io, const DataEdit &edit,
|
|||
if (next) {
|
||||
negative = *next == '-';
|
||||
if (negative || *next == '+') {
|
||||
io.GotChar();
|
||||
io.SkipSpaces(remaining);
|
||||
next = io.NextInField(remaining);
|
||||
}
|
||||
|
@ -453,6 +454,7 @@ bool EditDefaultCharacterInput(
|
|||
next = io.NextInField(remaining)) {
|
||||
if (skip > 0) {
|
||||
--skip;
|
||||
io.GotChar(-1);
|
||||
} else {
|
||||
*x++ = *next;
|
||||
--length;
|
||||
|
|
|
@ -925,6 +925,8 @@ bool IONAME(OutputUnformattedBlock)(Cookie cookie, const char *x,
|
|||
if (auto *unf{io.get_if<
|
||||
ExternalUnformattedIoStatementState<Direction::Output>>()}) {
|
||||
return unf->Emit(x, length, elementBytes);
|
||||
} else if (auto *inq{io.get_if<InquireIOLengthState>()}) {
|
||||
return inq->Emit(x, length, elementBytes);
|
||||
}
|
||||
io.GetIoErrorHandler().Crash("OutputUnformattedBlock() called for an I/O "
|
||||
"statement that is not unformatted output");
|
||||
|
@ -1080,6 +1082,27 @@ bool IONAME(InputLogical)(Cookie cookie, bool &truth) {
|
|||
return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
|
||||
}
|
||||
|
||||
std::size_t IONAME(GetSize)(Cookie cookie) {
|
||||
IoStatementState &io{*cookie};
|
||||
if (const auto *formatted{
|
||||
io.get_if<FormattedIoStatementState<Direction::Input>>()}) {
|
||||
return formatted->GetEditDescriptorChars();
|
||||
}
|
||||
io.GetIoErrorHandler().Crash(
|
||||
"GetIoSize() called for an I/O statement that is not a formatted READ()");
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t IONAME(GetIoLength)(Cookie cookie) {
|
||||
IoStatementState &io{*cookie};
|
||||
if (const auto *inq{io.get_if<InquireIOLengthState>()}) {
|
||||
return inq->bytes();
|
||||
}
|
||||
io.GetIoErrorHandler().Crash("GetIoLength() called for an I/O statement that "
|
||||
"is not INQUIRE(IOLENGTH=)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IONAME(GetIoMsg)(Cookie cookie, char *msg, std::size_t length) {
|
||||
IoErrorHandler &handler{cookie->GetIoErrorHandler()};
|
||||
if (handler.InError()) { // leave "msg" alone when no error
|
||||
|
|
|
@ -521,6 +521,7 @@ std::optional<char32_t> IoStatementState::SkipSpaces(
|
|||
}
|
||||
HandleRelativePosition(1);
|
||||
if (remaining) {
|
||||
GotChar();
|
||||
--*remaining;
|
||||
}
|
||||
} else {
|
||||
|
@ -556,6 +557,7 @@ std::optional<char32_t> IoStatementState::NextInField(
|
|||
if (auto next{GetCurrentChar()}) {
|
||||
--*remaining;
|
||||
HandleRelativePosition(1);
|
||||
GotChar();
|
||||
return next;
|
||||
}
|
||||
const ConnectionState &connection{GetConnectionState()};
|
||||
|
@ -610,6 +612,25 @@ bool IoStatementState::Inquire(InquiryKeywordHash inquiry, std::int64_t &n) {
|
|||
return std::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) {
|
||||
|
@ -1325,4 +1346,25 @@ 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 * elementBytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InquireIOLengthState::Emit(const char *p, std::size_t n) {
|
||||
bytes_ += sizeof *p * n;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InquireIOLengthState::Emit(const char16_t *p, std::size_t n) {
|
||||
bytes_ += sizeof *p * n;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InquireIOLengthState::Emit(const char32_t *p, std::size_t n) {
|
||||
bytes_ += sizeof *p * n;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Fortran::runtime::io
|
||||
|
|
|
@ -52,7 +52,19 @@ struct OutputStatementState {};
|
|||
template <Direction D>
|
||||
using IoDirectionState = std::conditional_t<D == Direction::Input,
|
||||
InputStatementState, OutputStatementState>;
|
||||
struct FormattedIoStatementState {};
|
||||
|
||||
// Common state for all kinds of formatted I/O
|
||||
template <Direction D> class FormattedIoStatementState {};
|
||||
template <> class FormattedIoStatementState<Direction::Input> {
|
||||
public:
|
||||
std::size_t GetEditDescriptorChars() const;
|
||||
void GotChar(int);
|
||||
|
||||
private:
|
||||
// Account of characters read for edit descriptors (i.e., formatted I/O
|
||||
// with a FORMAT, not list-directed or NAMELIST), not including padding.
|
||||
std::size_t chars_{0}; // for READ(SIZE=)
|
||||
};
|
||||
|
||||
// The Cookie type in the I/O API is a pointer (for C) to this class.
|
||||
class IoStatementState {
|
||||
|
@ -83,6 +95,7 @@ public:
|
|||
bool Inquire(InquiryKeywordHash, bool &);
|
||||
bool Inquire(InquiryKeywordHash, std::int64_t, bool &); // PENDING=
|
||||
bool Inquire(InquiryKeywordHash, std::int64_t &);
|
||||
void GotChar(signed int = 1); // for READ(SIZE=); can be <0
|
||||
|
||||
MutableModes &mutableModes();
|
||||
ConnectionState &GetConnectionState();
|
||||
|
@ -115,8 +128,7 @@ public:
|
|||
std::optional<char32_t> GetNextNonBlank();
|
||||
|
||||
template <Direction D> void CheckFormattedStmtType(const char *name) {
|
||||
if (!get_if<FormattedIoStatementState>() ||
|
||||
!get_if<IoDirectionState<D>>()) {
|
||||
if (!get_if<FormattedIoStatementState<D>>()) {
|
||||
GetIoErrorHandler().Crash(
|
||||
"%s called for I/O statement that is not formatted %s", name,
|
||||
D == Direction::Output ? "output" : "input");
|
||||
|
@ -191,7 +203,7 @@ struct IoStatementBase : public IoErrorHandler {
|
|||
template <Direction> class ListDirectedStatementState;
|
||||
template <>
|
||||
class ListDirectedStatementState<Direction::Output>
|
||||
: public FormattedIoStatementState {
|
||||
: public FormattedIoStatementState<Direction::Output> {
|
||||
public:
|
||||
bool EmitLeadingSpaceOrAdvance(
|
||||
IoStatementState &, std::size_t = 1, bool isCharacter = false);
|
||||
|
@ -209,7 +221,7 @@ private:
|
|||
};
|
||||
template <>
|
||||
class ListDirectedStatementState<Direction::Input>
|
||||
: public FormattedIoStatementState {
|
||||
: public FormattedIoStatementState<Direction::Input> {
|
||||
public:
|
||||
// Skips value separators, handles repetition and null values.
|
||||
// Vacant when '/' appears; present with descriptor == ListDirectedNullValue
|
||||
|
@ -269,7 +281,7 @@ protected:
|
|||
template <Direction DIR, typename CHAR>
|
||||
class InternalFormattedIoStatementState
|
||||
: public InternalIoStatementState<DIR, CHAR>,
|
||||
public FormattedIoStatementState {
|
||||
public FormattedIoStatementState<DIR> {
|
||||
public:
|
||||
using CharType = CHAR;
|
||||
using typename InternalIoStatementState<DIR, CharType>::Buffer;
|
||||
|
@ -353,8 +365,9 @@ private:
|
|||
};
|
||||
|
||||
template <Direction DIR, typename CHAR>
|
||||
class ExternalFormattedIoStatementState : public ExternalIoStatementState<DIR>,
|
||||
public FormattedIoStatementState {
|
||||
class ExternalFormattedIoStatementState
|
||||
: public ExternalIoStatementState<DIR>,
|
||||
public FormattedIoStatementState<DIR> {
|
||||
public:
|
||||
using CharType = CHAR;
|
||||
ExternalFormattedIoStatementState(ExternalFileUnit &, const CharType *format,
|
||||
|
@ -411,7 +424,7 @@ private:
|
|||
|
||||
template <Direction DIR, typename CHAR>
|
||||
class ChildFormattedIoStatementState : public ChildIoStatementState<DIR>,
|
||||
public FormattedIoStatementState {
|
||||
public FormattedIoStatementState<DIR> {
|
||||
public:
|
||||
using CharType = CHAR;
|
||||
ChildFormattedIoStatementState(ChildIo &, const CharType *format,
|
||||
|
@ -584,6 +597,10 @@ class InquireIOLengthState : public NoUnitIoStatementState,
|
|||
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);
|
||||
bool Emit(const char *, std::size_t);
|
||||
bool Emit(const char16_t *, std::size_t chars);
|
||||
bool Emit(const char32_t *, std::size_t chars);
|
||||
|
||||
private:
|
||||
std::size_t bytes_{0};
|
||||
|
|
|
@ -768,8 +768,13 @@ void ChildIo::EndIoStatement() {
|
|||
|
||||
bool ChildIo::CheckFormattingAndDirection(Terminator &terminator,
|
||||
const char *what, bool unformatted, Direction direction) {
|
||||
bool parentIsUnformatted{!parent_.get_if<FormattedIoStatementState>()};
|
||||
bool parentIsInput{!parent_.get_if<IoDirectionState<Direction::Output>>()};
|
||||
bool parentIsFormatted{parentIsInput
|
||||
? parent_.get_if<FormattedIoStatementState<Direction::Input>>() !=
|
||||
nullptr
|
||||
: parent_.get_if<FormattedIoStatementState<Direction::Output>>() !=
|
||||
nullptr};
|
||||
bool parentIsUnformatted{!parentIsFormatted};
|
||||
if (unformatted != parentIsUnformatted) {
|
||||
terminator.Crash("Child %s attempted on %s parent I/O unit", what,
|
||||
parentIsUnformatted ? "unformatted" : "formatted");
|
||||
|
|
|
@ -41,6 +41,15 @@ TEST(ExternalIOTests, TestDirectUnformatted) {
|
|||
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
|
||||
<< "EndIoStatement() for OpenNewUnit";
|
||||
|
||||
// INQUIRE(IOLENGTH=) j
|
||||
io = IONAME(BeginInquireIoLength)(__FILE__, __LINE__);
|
||||
ASSERT_TRUE(IONAME(OutputUnformattedBlock)(
|
||||
io, reinterpret_cast<const char *>(&buffer), 1, recl))
|
||||
<< "OutputUnformattedBlock() for InquireIoLength";
|
||||
ASSERT_EQ(IONAME(GetIoLength)(io), recl) << "GetIoLength";
|
||||
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
|
||||
<< "EndIoStatement() for InquireIoLength";
|
||||
|
||||
static constexpr int records{10};
|
||||
for (int j{1}; j <= records; ++j) {
|
||||
// WRITE(UNIT=unit,REC=j) j
|
||||
|
@ -49,7 +58,7 @@ TEST(ExternalIOTests, TestDirectUnformatted) {
|
|||
|
||||
buffer = j;
|
||||
ASSERT_TRUE(IONAME(OutputUnformattedBlock)(
|
||||
io, reinterpret_cast<const char *>(&buffer), recl, recl))
|
||||
io, reinterpret_cast<const char *>(&buffer), 1, recl))
|
||||
<< "OutputUnformattedBlock()";
|
||||
|
||||
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
|
||||
|
@ -61,7 +70,7 @@ TEST(ExternalIOTests, TestDirectUnformatted) {
|
|||
io = IONAME(BeginUnformattedInput)(unit, __FILE__, __LINE__);
|
||||
ASSERT_TRUE(IONAME(SetRec)(io, j)) << "SetRec(" << j << ')';
|
||||
ASSERT_TRUE(IONAME(InputUnformattedBlock)(
|
||||
io, reinterpret_cast<char *>(&buffer), recl, recl))
|
||||
io, reinterpret_cast<char *>(&buffer), 1, recl))
|
||||
<< "InputUnformattedBlock()";
|
||||
|
||||
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
|
||||
|
@ -158,6 +167,17 @@ TEST(ExternalIOTests, TestSequentialFixedUnformatted) {
|
|||
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
|
||||
<< "EndIoStatement() for OpenNewUnit";
|
||||
|
||||
// INQUIRE(IOLENGTH=) j, ...
|
||||
io = IONAME(BeginInquireIoLength)(__FILE__, __LINE__);
|
||||
for (int j{1}; j <= 3; ++j) {
|
||||
ASSERT_TRUE(IONAME(OutputUnformattedBlock)(
|
||||
io, reinterpret_cast<const char *>(&buffer), 1, recl))
|
||||
<< "OutputUnformattedBlock() for InquireIoLength";
|
||||
}
|
||||
ASSERT_EQ(IONAME(GetIoLength)(io), 3 * recl) << "GetIoLength";
|
||||
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
|
||||
<< "EndIoStatement() for InquireIoLength";
|
||||
|
||||
static const int records{10};
|
||||
for (int j{1}; j <= records; ++j) {
|
||||
// DO J=1,RECORDS; WRITE(UNIT=unit) j; END DO
|
||||
|
@ -417,7 +437,7 @@ TEST(ExternalIOTests, TestSequentialVariableFormatted) {
|
|||
<< "EndIoStatement() for Backspace (before read)";
|
||||
|
||||
std::snprintf(fmt, sizeof fmt, "(%dI4)", j);
|
||||
// READ(UNIT=unit,FMT=fmt) n; check
|
||||
// READ(UNIT=unit,FMT=fmt,SIZE=chars) n; check
|
||||
io = IONAME(BeginExternalFormattedInput)(
|
||||
fmt, std::strlen(fmt), unit, __FILE__, __LINE__);
|
||||
|
||||
|
@ -426,6 +446,9 @@ TEST(ExternalIOTests, TestSequentialVariableFormatted) {
|
|||
ASSERT_TRUE(IONAME(InputInteger)(io, check[k])) << "InputInteger()";
|
||||
}
|
||||
|
||||
std::size_t chars{IONAME(GetSize)(io)};
|
||||
ASSERT_EQ(chars, j * 4u)
|
||||
<< "GetSize()=" << chars << ", expected " << (j * 4u) << '\n';
|
||||
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
|
||||
<< "EndIoStatement() for InputInteger";
|
||||
for (int k{0}; k < j; ++k) {
|
||||
|
|
Loading…
Reference in New Issue