xref: /llvm-project/flang/runtime/io-stmt.cpp (revision c893e3d02d1f7b67880090485a030b79741bba1c)
1 //===-- runtime/io-stmt.cpp -----------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "io-stmt.h"
10 #include "connection.h"
11 #include "emit-encoded.h"
12 #include "format.h"
13 #include "tools.h"
14 #include "unit.h"
15 #include "utf.h"
16 #include "flang/Runtime/memory.h"
17 #include <algorithm>
18 #include <cstdio>
19 #include <cstring>
20 #include <limits>
21 #include <type_traits>
22 
23 namespace Fortran::runtime::io {
24 RT_OFFLOAD_API_GROUP_BEGIN
25 
26 bool IoStatementBase::Emit(const char *, std::size_t, std::size_t) {
27   return false;
28 }
29 
30 std::size_t IoStatementBase::GetNextInputBytes(const char *&p) {
31   p = nullptr;
32   return 0;
33 }
34 
35 std::size_t IoStatementBase::ViewBytesInRecord(
36     const char *&p, bool forward) const {
37   p = nullptr;
38   return 0;
39 }
40 
41 bool IoStatementBase::AdvanceRecord(int) { return false; }
42 
43 void IoStatementBase::BackspaceRecord() {}
44 
45 bool IoStatementBase::Receive(char *, std::size_t, std::size_t) {
46   return false;
47 }
48 
49 Fortran::common::optional<DataEdit> IoStatementBase::GetNextDataEdit(
50     IoStatementState &, int) {
51   return Fortran::common::nullopt;
52 }
53 
54 bool IoStatementBase::BeginReadingRecord() { return true; }
55 
56 void IoStatementBase::FinishReadingRecord() {}
57 
58 void IoStatementBase::HandleAbsolutePosition(std::int64_t) {}
59 
60 void IoStatementBase::HandleRelativePosition(std::int64_t) {}
61 
62 std::int64_t IoStatementBase::InquirePos() { return 0; }
63 
64 ExternalFileUnit *IoStatementBase::GetExternalFileUnit() const {
65   return nullptr;
66 }
67 
68 bool IoStatementBase::Inquire(InquiryKeywordHash, char *, std::size_t) {
69   return false;
70 }
71 
72 bool IoStatementBase::Inquire(InquiryKeywordHash, bool &) { return false; }
73 
74 bool IoStatementBase::Inquire(InquiryKeywordHash, std::int64_t, bool &) {
75   return false;
76 }
77 
78 bool IoStatementBase::Inquire(InquiryKeywordHash, std::int64_t &) {
79   return false;
80 }
81 
82 void IoStatementBase::BadInquiryKeywordHashCrash(InquiryKeywordHash inquiry) {
83   char buffer[16];
84   const char *decode{InquiryKeywordHashDecode(buffer, sizeof buffer, inquiry)};
85   Crash("Bad InquiryKeywordHash 0x%x (%s)", inquiry,
86       decode ? decode : "(cannot decode)");
87 }
88 
89 template <Direction DIR>
90 InternalIoStatementState<DIR>::InternalIoStatementState(
91     Buffer scalar, std::size_t length, const char *sourceFile, int sourceLine)
92     : IoStatementBase{sourceFile, sourceLine}, unit_{scalar, length, 1} {}
93 
94 template <Direction DIR>
95 InternalIoStatementState<DIR>::InternalIoStatementState(
96     const Descriptor &d, const char *sourceFile, int sourceLine)
97     : IoStatementBase{sourceFile, sourceLine}, unit_{d, *this} {}
98 
99 template <Direction DIR>
100 bool InternalIoStatementState<DIR>::Emit(
101     const char *data, std::size_t bytes, std::size_t /*elementBytes*/) {
102   if constexpr (DIR == Direction::Input) {
103     Crash("InternalIoStatementState<Direction::Input>::Emit() called");
104     return false;
105   }
106   return unit_.Emit(data, bytes, *this);
107 }
108 
109 template <Direction DIR>
110 std::size_t InternalIoStatementState<DIR>::GetNextInputBytes(const char *&p) {
111   return unit_.GetNextInputBytes(p, *this);
112 }
113 
114 // InternalIoStatementState<DIR>::ViewBytesInRecord() not needed or defined
115 
116 template <Direction DIR>
117 bool InternalIoStatementState<DIR>::AdvanceRecord(int n) {
118   while (n-- > 0) {
119     if (!unit_.AdvanceRecord(*this)) {
120       return false;
121     }
122   }
123   return true;
124 }
125 
126 template <Direction DIR> void InternalIoStatementState<DIR>::BackspaceRecord() {
127   unit_.BackspaceRecord(*this);
128 }
129 
130 template <Direction DIR> int InternalIoStatementState<DIR>::EndIoStatement() {
131   auto result{IoStatementBase::EndIoStatement()};
132   if (free_) {
133     FreeMemory(this);
134   }
135   return result;
136 }
137 
138 template <Direction DIR>
139 void InternalIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) {
140   return unit_.HandleAbsolutePosition(n);
141 }
142 
143 template <Direction DIR>
144 void InternalIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) {
145   return unit_.HandleRelativePosition(n);
146 }
147 
148 template <Direction DIR>
149 std::int64_t InternalIoStatementState<DIR>::InquirePos() {
150   return unit_.InquirePos();
151 }
152 
153 template <Direction DIR, typename CHAR>
154 RT_API_ATTRS
155 InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState(
156     Buffer buffer, std::size_t length, const CharType *format,
157     std::size_t formatLength, const Descriptor *formatDescriptor,
158     const char *sourceFile, int sourceLine)
159     : InternalIoStatementState<DIR>{buffer, length, sourceFile, sourceLine},
160       ioStatementState_{*this},
161       format_{*this, format, formatLength, formatDescriptor} {}
162 
163 template <Direction DIR, typename CHAR>
164 RT_API_ATTRS
165 InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState(
166     const Descriptor &d, const CharType *format, std::size_t formatLength,
167     const Descriptor *formatDescriptor, const char *sourceFile, int sourceLine)
168     : InternalIoStatementState<DIR>{d, sourceFile, sourceLine},
169       ioStatementState_{*this},
170       format_{*this, format, formatLength, formatDescriptor} {}
171 
172 template <Direction DIR, typename CHAR>
173 void InternalFormattedIoStatementState<DIR, CHAR>::CompleteOperation() {
174   if (!this->completedOperation()) {
175     if constexpr (DIR == Direction::Output) {
176       format_.Finish(*this);
177       unit_.AdvanceRecord(*this);
178     }
179     IoStatementBase::CompleteOperation();
180   }
181 }
182 
183 template <Direction DIR, typename CHAR>
184 int InternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
185   CompleteOperation();
186   return InternalIoStatementState<DIR>::EndIoStatement();
187 }
188 
189 template <Direction DIR>
190 InternalListIoStatementState<DIR>::InternalListIoStatementState(
191     Buffer buffer, std::size_t length, const char *sourceFile, int sourceLine)
192     : InternalIoStatementState<DIR>{buffer, length, sourceFile, sourceLine},
193       ioStatementState_{*this} {}
194 
195 template <Direction DIR>
196 InternalListIoStatementState<DIR>::InternalListIoStatementState(
197     const Descriptor &d, const char *sourceFile, int sourceLine)
198     : InternalIoStatementState<DIR>{d, sourceFile, sourceLine},
199       ioStatementState_{*this} {}
200 
201 template <Direction DIR>
202 void InternalListIoStatementState<DIR>::CompleteOperation() {
203   if (!this->completedOperation()) {
204     if constexpr (DIR == Direction::Output) {
205       if (unit_.furthestPositionInRecord > 0) {
206         unit_.AdvanceRecord(*this);
207       }
208     }
209     IoStatementBase::CompleteOperation();
210   }
211 }
212 
213 template <Direction DIR>
214 int InternalListIoStatementState<DIR>::EndIoStatement() {
215   CompleteOperation();
216   if constexpr (DIR == Direction::Input) {
217     if (int status{ListDirectedStatementState<DIR>::EndIoStatement()};
218         status != IostatOk) {
219       return status;
220     }
221   }
222   return InternalIoStatementState<DIR>::EndIoStatement();
223 }
224 
225 ExternalIoStatementBase::ExternalIoStatementBase(
226     ExternalFileUnit &unit, const char *sourceFile, int sourceLine)
227     : IoStatementBase{sourceFile, sourceLine}, unit_{unit} {}
228 
229 MutableModes &ExternalIoStatementBase::mutableModes() {
230   if (const ChildIo * child{unit_.GetChildIo()}) {
231 #if !defined(RT_DEVICE_AVOID_RECURSION)
232     return child->parent().mutableModes();
233 #else
234     ReportUnsupportedChildIo();
235 #endif
236   }
237   return unit_.modes;
238 }
239 
240 ConnectionState &ExternalIoStatementBase::GetConnectionState() { return unit_; }
241 
242 int ExternalIoStatementBase::EndIoStatement() {
243   CompleteOperation();
244   auto result{IoStatementBase::EndIoStatement()};
245 #if !defined(RT_USE_PSEUDO_FILE_UNIT)
246   auto unitNumber{unit_.unitNumber()};
247   unit_.EndIoStatement(); // annihilates *this in unit_.u_
248   if (destroy_) {
249     if (ExternalFileUnit *
250         toClose{ExternalFileUnit::LookUpForClose(unitNumber)}) {
251       toClose->Close(CloseStatus::Delete, *this);
252       toClose->DestroyClosed();
253     }
254   }
255 #else
256   // Fetch the unit pointer before *this disappears.
257   ExternalFileUnit *unitPtr{&unit_};
258   // The pseudo file units are dynamically allocated
259   // and are not tracked in the unit map.
260   // They have to be destructed and deallocated here.
261   unitPtr->~ExternalFileUnit();
262   FreeMemory(unitPtr);
263 #endif
264   return result;
265 }
266 
267 void ExternalIoStatementBase::SetAsynchronous() {
268   asynchronousID_ = unit().GetAsynchronousId(*this);
269 }
270 
271 std::int64_t ExternalIoStatementBase::InquirePos() {
272   return unit_.InquirePos();
273 }
274 
275 void OpenStatementState::set_path(const char *path, std::size_t length) {
276   pathLength_ = TrimTrailingSpaces(path, length);
277   path_ = SaveDefaultCharacter(path, pathLength_, *this);
278 }
279 
280 void OpenStatementState::CompleteOperation() {
281   if (completedOperation()) {
282     return;
283   }
284   if (position_) {
285     if (access_ && *access_ == Access::Direct) {
286       SignalError("POSITION= may not be set with ACCESS='DIRECT'");
287       position_.reset();
288     }
289   }
290   if (status_) { // 12.5.6.10
291     if ((*status_ == OpenStatus::New || *status_ == OpenStatus::Replace) &&
292         !path_.get()) {
293       SignalError("FILE= required on OPEN with STATUS='NEW' or 'REPLACE'");
294     } else if (*status_ == OpenStatus::Scratch && path_.get()) {
295       SignalError("FILE= may not appear on OPEN with STATUS='SCRATCH'");
296     }
297   }
298   // F'2023 12.5.6.13 - NEWUNIT= requires either FILE= or STATUS='SCRATCH'
299   if (isNewUnit_ && !path_.get() &&
300       status_.value_or(OpenStatus::Unknown) != OpenStatus::Scratch) {
301     SignalError(IostatBadNewUnit);
302     status_ = OpenStatus::Scratch; // error recovery
303   }
304   if (path_.get() || wasExtant_ ||
305       (status_ && *status_ == OpenStatus::Scratch)) {
306     if (unit().OpenUnit(status_, action_, position_.value_or(Position::AsIs),
307             std::move(path_), pathLength_, convert_, *this)) {
308       wasExtant_ = false; // existing unit was closed
309     }
310   } else {
311     unit().OpenAnonymousUnit(
312         status_, action_, position_.value_or(Position::AsIs), convert_, *this);
313   }
314   if (access_) {
315     if (*access_ != unit().access) {
316       if (wasExtant_) {
317         SignalError("ACCESS= may not be changed on an open unit");
318         access_.reset();
319       }
320     }
321     if (access_) {
322       unit().access = *access_;
323     }
324   }
325   if (!unit().isUnformatted) {
326     unit().isUnformatted = isUnformatted_;
327   }
328   if (isUnformatted_ && *isUnformatted_ != *unit().isUnformatted) {
329     if (wasExtant_) {
330       SignalError("FORM= may not be changed on an open unit");
331     }
332     unit().isUnformatted = *isUnformatted_;
333   }
334   if (!unit().isUnformatted) {
335     // Set default format (C.7.4 point 2).
336     unit().isUnformatted = unit().access != Access::Sequential;
337   }
338   if (!wasExtant_ && InError()) {
339     // Release the new unit on failure
340     set_destroy();
341   }
342   IoStatementBase::CompleteOperation();
343 }
344 
345 int OpenStatementState::EndIoStatement() {
346   CompleteOperation();
347   return ExternalIoStatementBase::EndIoStatement();
348 }
349 
350 int CloseStatementState::EndIoStatement() {
351   CompleteOperation();
352   int result{ExternalIoStatementBase::EndIoStatement()};
353   unit().CloseUnit(status_, *this);
354   unit().DestroyClosed();
355   return result;
356 }
357 
358 void NoUnitIoStatementState::CompleteOperation() {
359   SignalPendingError();
360   IoStatementBase::CompleteOperation();
361 }
362 
363 int NoUnitIoStatementState::EndIoStatement() {
364   CompleteOperation();
365   auto result{IoStatementBase::EndIoStatement()};
366   FreeMemory(this);
367   return result;
368 }
369 
370 template <Direction DIR>
371 ExternalIoStatementState<DIR>::ExternalIoStatementState(
372     ExternalFileUnit &unit, const char *sourceFile, int sourceLine)
373     : ExternalIoStatementBase{unit, sourceFile, sourceLine}, mutableModes_{
374                                                                  unit.modes} {
375   if constexpr (DIR == Direction::Output) {
376     // If the last statement was a non-advancing IO input statement, the unit
377     // furthestPositionInRecord was not advanced, but the positionInRecord may
378     // have been advanced. Advance furthestPositionInRecord here to avoid
379     // overwriting the part of the record that has been read with blanks.
380     unit.furthestPositionInRecord =
381         std::max(unit.furthestPositionInRecord, unit.positionInRecord);
382   }
383 }
384 
385 template <Direction DIR>
386 void ExternalIoStatementState<DIR>::CompleteOperation() {
387   if (completedOperation()) {
388     return;
389   }
390   if constexpr (DIR == Direction::Input) {
391     BeginReadingRecord(); // in case there were no I/O items
392     if (mutableModes().nonAdvancing && !InError()) {
393       unit().leftTabLimit = unit().furthestPositionInRecord;
394     } else {
395       FinishReadingRecord();
396     }
397   } else { // output
398     if (mutableModes().nonAdvancing) {
399       // Make effects of positioning past the last Emit() visible with blanks.
400       if (unit().positionInRecord > unit().furthestPositionInRecord) {
401         unit().Emit("", 0, 1, *this); // Emit() will pad
402       }
403       unit().leftTabLimit = unit().positionInRecord;
404     } else {
405       unit().AdvanceRecord(*this);
406     }
407     unit().FlushIfTerminal(*this);
408   }
409   return IoStatementBase::CompleteOperation();
410 }
411 
412 template <Direction DIR> int ExternalIoStatementState<DIR>::EndIoStatement() {
413   CompleteOperation();
414   return ExternalIoStatementBase::EndIoStatement();
415 }
416 
417 template <Direction DIR>
418 bool ExternalIoStatementState<DIR>::Emit(
419     const char *data, std::size_t bytes, std::size_t elementBytes) {
420   if constexpr (DIR == Direction::Input) {
421     Crash("ExternalIoStatementState::Emit(char) called for input statement");
422   }
423   return unit().Emit(data, bytes, elementBytes, *this);
424 }
425 
426 template <Direction DIR>
427 std::size_t ExternalIoStatementState<DIR>::GetNextInputBytes(const char *&p) {
428   return unit().GetNextInputBytes(p, *this);
429 }
430 
431 template <Direction DIR>
432 std::size_t ExternalIoStatementState<DIR>::ViewBytesInRecord(
433     const char *&p, bool forward) const {
434   return unit().ViewBytesInRecord(p, forward);
435 }
436 
437 template <Direction DIR>
438 bool ExternalIoStatementState<DIR>::AdvanceRecord(int n) {
439   while (n-- > 0) {
440     if (!unit().AdvanceRecord(*this)) {
441       return false;
442     }
443   }
444   return true;
445 }
446 
447 template <Direction DIR> void ExternalIoStatementState<DIR>::BackspaceRecord() {
448   unit().BackspaceRecord(*this);
449 }
450 
451 template <Direction DIR>
452 void ExternalIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) {
453   return unit().HandleAbsolutePosition(n);
454 }
455 
456 template <Direction DIR>
457 void ExternalIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) {
458   return unit().HandleRelativePosition(n);
459 }
460 
461 template <Direction DIR>
462 bool ExternalIoStatementState<DIR>::BeginReadingRecord() {
463   if constexpr (DIR == Direction::Input) {
464     return unit().BeginReadingRecord(*this);
465   } else {
466     Crash("ExternalIoStatementState<Direction::Output>::BeginReadingRecord() "
467           "called");
468     return false;
469   }
470 }
471 
472 template <Direction DIR>
473 void ExternalIoStatementState<DIR>::FinishReadingRecord() {
474   if constexpr (DIR == Direction::Input) {
475     unit().FinishReadingRecord(*this);
476   } else {
477     Crash("ExternalIoStatementState<Direction::Output>::FinishReadingRecord() "
478           "called");
479   }
480 }
481 
482 template <Direction DIR, typename CHAR>
483 ExternalFormattedIoStatementState<DIR, CHAR>::ExternalFormattedIoStatementState(
484     ExternalFileUnit &unit, const CHAR *format, std::size_t formatLength,
485     const Descriptor *formatDescriptor, const char *sourceFile, int sourceLine)
486     : ExternalIoStatementState<DIR>{unit, sourceFile, sourceLine},
487       format_{*this, format, formatLength, formatDescriptor} {}
488 
489 template <Direction DIR, typename CHAR>
490 void ExternalFormattedIoStatementState<DIR, CHAR>::CompleteOperation() {
491   if (this->completedOperation()) {
492     return;
493   }
494   if constexpr (DIR == Direction::Input) {
495     this->BeginReadingRecord(); // in case there were no I/O items
496   }
497   format_.Finish(*this);
498   return ExternalIoStatementState<DIR>::CompleteOperation();
499 }
500 
501 template <Direction DIR, typename CHAR>
502 int ExternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
503   CompleteOperation();
504   return ExternalIoStatementState<DIR>::EndIoStatement();
505 }
506 
507 Fortran::common::optional<DataEdit> IoStatementState::GetNextDataEdit(int n) {
508   return common::visit(
509       [&](auto &x) { return x.get().GetNextDataEdit(*this, n); }, u_);
510 }
511 
512 bool IoStatementState::Emit(
513     const char *data, std::size_t bytes, std::size_t elementBytes) {
514   return common::visit(
515       [=](auto &x) { return x.get().Emit(data, bytes, elementBytes); }, u_);
516 }
517 
518 bool IoStatementState::Receive(
519     char *data, std::size_t n, std::size_t elementBytes) {
520   return common::visit(
521       [=](auto &x) { return x.get().Receive(data, n, elementBytes); }, u_);
522 }
523 
524 std::size_t IoStatementState::GetNextInputBytes(const char *&p) {
525   return common::visit(
526       [&](auto &x) { return x.get().GetNextInputBytes(p); }, u_);
527 }
528 
529 bool IoStatementState::AdvanceRecord(int n) {
530   return common::visit([=](auto &x) { return x.get().AdvanceRecord(n); }, u_);
531 }
532 
533 void IoStatementState::BackspaceRecord() {
534   common::visit([](auto &x) { x.get().BackspaceRecord(); }, u_);
535 }
536 
537 void IoStatementState::HandleRelativePosition(std::int64_t n) {
538   common::visit([=](auto &x) { x.get().HandleRelativePosition(n); }, u_);
539 }
540 
541 void IoStatementState::HandleAbsolutePosition(std::int64_t n) {
542   common::visit([=](auto &x) { x.get().HandleAbsolutePosition(n); }, u_);
543 }
544 
545 void IoStatementState::CompleteOperation() {
546   common::visit([](auto &x) { x.get().CompleteOperation(); }, u_);
547 }
548 
549 int IoStatementState::EndIoStatement() {
550   return common::visit([](auto &x) { return x.get().EndIoStatement(); }, u_);
551 }
552 
553 ConnectionState &IoStatementState::GetConnectionState() {
554   return common::visit(
555       [](auto &x) -> ConnectionState & { return x.get().GetConnectionState(); },
556       u_);
557 }
558 
559 MutableModes &IoStatementState::mutableModes() {
560   return common::visit(
561       [](auto &x) -> MutableModes & { return x.get().mutableModes(); }, u_);
562 }
563 
564 bool IoStatementState::BeginReadingRecord() {
565   return common::visit(
566       [](auto &x) { return x.get().BeginReadingRecord(); }, u_);
567 }
568 
569 IoErrorHandler &IoStatementState::GetIoErrorHandler() const {
570   return common::visit(
571       [](auto &x) -> IoErrorHandler & {
572         return static_cast<IoErrorHandler &>(x.get());
573       },
574       u_);
575 }
576 
577 ExternalFileUnit *IoStatementState::GetExternalFileUnit() const {
578   return common::visit(
579       [](auto &x) { return x.get().GetExternalFileUnit(); }, u_);
580 }
581 
582 Fortran::common::optional<char32_t> IoStatementState::GetCurrentChar(
583     std::size_t &byteCount) {
584   const char *p{nullptr};
585   std::size_t bytes{GetNextInputBytes(p)};
586   if (bytes == 0) {
587     byteCount = 0;
588     return Fortran::common::nullopt;
589   } else {
590     const ConnectionState &connection{GetConnectionState()};
591     if (connection.isUTF8) {
592       std::size_t length{MeasureUTF8Bytes(*p)};
593       if (length <= bytes) {
594         if (auto result{DecodeUTF8(p)}) {
595           byteCount = length;
596           return result;
597         }
598       }
599       GetIoErrorHandler().SignalError(IostatUTF8Decoding);
600       // Error recovery: return the next byte
601     } else if (connection.internalIoCharKind > 1) {
602       byteCount = connection.internalIoCharKind;
603       if (byteCount == 2) {
604         return *reinterpret_cast<const char16_t *>(p);
605       } else {
606         return *reinterpret_cast<const char32_t *>(p);
607       }
608     }
609     byteCount = 1;
610     return *p;
611   }
612 }
613 
614 Fortran::common::optional<char32_t> IoStatementState::NextInField(
615     Fortran::common::optional<int> &remaining, const DataEdit &edit) {
616   std::size_t byteCount{0};
617   if (!remaining) { // Stream, list-directed, or NAMELIST
618     if (auto next{GetCurrentChar(byteCount)}) {
619       if (edit.IsListDirected()) {
620         // list-directed or NAMELIST: check for separators
621         switch (*next) {
622         case ' ':
623         case '\t':
624         case '/':
625         case '(':
626         case ')':
627         case '\'':
628         case '"':
629         case '*':
630         case '\n': // for stream access
631           return Fortran::common::nullopt;
632         case '&':
633         case '$':
634           if (edit.IsNamelist()) {
635             return Fortran::common::nullopt;
636           }
637           break;
638         case ',':
639           if (!(edit.modes.editingFlags & decimalComma)) {
640             return Fortran::common::nullopt;
641           }
642           break;
643         case ';':
644           if (edit.modes.editingFlags & decimalComma) {
645             return Fortran::common::nullopt;
646           }
647           break;
648         default:
649           break;
650         }
651       }
652       HandleRelativePosition(byteCount);
653       GotChar(byteCount);
654       return next;
655     }
656   } else if (*remaining > 0) {
657     if (auto next{GetCurrentChar(byteCount)}) {
658       if (byteCount > static_cast<std::size_t>(*remaining)) {
659         return Fortran::common::nullopt;
660       }
661       *remaining -= byteCount;
662       HandleRelativePosition(byteCount);
663       GotChar(byteCount);
664       return next;
665     }
666     if (CheckForEndOfRecord(0)) { // do padding
667       --*remaining;
668       return Fortran::common::optional<char32_t>{' '};
669     }
670   }
671   return Fortran::common::nullopt;
672 }
673 
674 bool IoStatementState::CheckForEndOfRecord(std::size_t afterReading) {
675   const ConnectionState &connection{GetConnectionState()};
676   if (!connection.IsAtEOF()) {
677     if (auto length{connection.EffectiveRecordLength()}) {
678       if (connection.positionInRecord +
679               static_cast<std::int64_t>(afterReading) >=
680           *length) {
681         IoErrorHandler &handler{GetIoErrorHandler()};
682         const auto &modes{mutableModes()};
683         if (modes.nonAdvancing) {
684           if (connection.access == Access::Stream &&
685               connection.unterminatedRecord) {
686             // Reading final unterminated record left by a
687             // non-advancing WRITE on a stream file prior to
688             // positioning or ENDFILE.
689             handler.SignalEnd();
690           } else {
691             handler.SignalEor();
692           }
693         } else if (!modes.pad) {
694           handler.SignalError(IostatRecordReadOverrun);
695         }
696         return modes.pad; // PAD='YES'
697       }
698     }
699   }
700   return false;
701 }
702 
703 bool IoStatementState::Inquire(
704     InquiryKeywordHash inquiry, char *out, std::size_t chars) {
705   return common::visit(
706       [&](auto &x) { return x.get().Inquire(inquiry, out, chars); }, u_);
707 }
708 
709 bool IoStatementState::Inquire(InquiryKeywordHash inquiry, bool &out) {
710   return common::visit(
711       [&](auto &x) { return x.get().Inquire(inquiry, out); }, u_);
712 }
713 
714 bool IoStatementState::Inquire(
715     InquiryKeywordHash inquiry, std::int64_t id, bool &out) {
716   return common::visit(
717       [&](auto &x) { return x.get().Inquire(inquiry, id, out); }, u_);
718 }
719 
720 bool IoStatementState::Inquire(InquiryKeywordHash inquiry, std::int64_t &n) {
721   return common::visit(
722       [&](auto &x) { return x.get().Inquire(inquiry, n); }, u_);
723 }
724 
725 std::int64_t IoStatementState::InquirePos() {
726   return common::visit([&](auto &x) { return x.get().InquirePos(); }, u_);
727 }
728 
729 void IoStatementState::GotChar(int n) {
730   if (auto *formattedIn{
731           get_if<FormattedIoStatementState<Direction::Input>>()}) {
732     formattedIn->GotChar(n);
733   } else {
734     GetIoErrorHandler().Crash("IoStatementState::GotChar() called for "
735                               "statement that is not formatted input");
736   }
737 }
738 
739 std::size_t
740 FormattedIoStatementState<Direction::Input>::GetEditDescriptorChars() const {
741   return chars_;
742 }
743 
744 void FormattedIoStatementState<Direction::Input>::GotChar(int n) {
745   chars_ += n;
746 }
747 
748 bool ListDirectedStatementState<Direction::Output>::EmitLeadingSpaceOrAdvance(
749     IoStatementState &io, std::size_t length, bool isCharacter) {
750   const ConnectionState &connection{io.GetConnectionState()};
751   int space{connection.positionInRecord == 0 ||
752       !(isCharacter && lastWasUndelimitedCharacter())};
753   set_lastWasUndelimitedCharacter(false);
754   if (connection.NeedAdvance(space + length)) {
755     return io.AdvanceRecord();
756   }
757   if (space) {
758     return EmitAscii(io, " ", 1);
759   }
760   return true;
761 }
762 
763 Fortran::common::optional<DataEdit>
764 ListDirectedStatementState<Direction::Output>::GetNextDataEdit(
765     IoStatementState &io, int maxRepeat) {
766   DataEdit edit;
767   edit.descriptor = DataEdit::ListDirected;
768   edit.repeat = maxRepeat;
769   edit.modes = io.mutableModes();
770   return edit;
771 }
772 
773 int ListDirectedStatementState<Direction::Input>::EndIoStatement() {
774   if (repeatPosition_) {
775     repeatPosition_->Cancel();
776   }
777   return IostatOk;
778 }
779 
780 Fortran::common::optional<DataEdit>
781 ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
782     IoStatementState &io, int maxRepeat) {
783   // N.B. list-directed transfers cannot be nonadvancing (C1221)
784   ConnectionState &connection{io.GetConnectionState()};
785   DataEdit edit;
786   edit.descriptor = DataEdit::ListDirected;
787   edit.repeat = 1; // may be overridden below
788   edit.modes = io.mutableModes();
789   if (hitSlash_) { // everything after '/' is nullified
790     edit.descriptor = DataEdit::ListDirectedNullValue;
791     return edit;
792   }
793   char32_t comma{','};
794   if (edit.modes.editingFlags & decimalComma) {
795     comma = ';';
796   }
797   std::size_t byteCount{0};
798   if (remaining_ > 0 && !realPart_) { // "r*c" repetition in progress
799     RUNTIME_CHECK(io.GetIoErrorHandler(), repeatPosition_.has_value());
800     repeatPosition_.reset(); // restores the saved position
801     if (!imaginaryPart_) {
802       edit.repeat = std::min<int>(remaining_, maxRepeat);
803       auto ch{io.GetCurrentChar(byteCount)};
804       if (!ch || *ch == ' ' || *ch == '\t' || *ch == comma) {
805         // "r*" repeated null
806         edit.descriptor = DataEdit::ListDirectedNullValue;
807       }
808     }
809     remaining_ -= edit.repeat;
810     if (remaining_ > 0) {
811       repeatPosition_.emplace(io);
812     }
813     if (!imaginaryPart_) {
814       return edit;
815     }
816   }
817   // Skip separators, handle a "r*c" repeat count; see 13.10.2 in Fortran 2018
818   if (imaginaryPart_) {
819     imaginaryPart_ = false;
820   } else if (realPart_) {
821     realPart_ = false;
822     imaginaryPart_ = true;
823     edit.descriptor = DataEdit::ListDirectedImaginaryPart;
824   }
825   auto ch{io.GetNextNonBlank(byteCount)};
826   if (ch && *ch == comma && eatComma_) {
827     // Consume comma & whitespace after previous item.
828     // This includes the comma between real and imaginary components
829     // in list-directed/NAMELIST complex input.
830     // (When DECIMAL='COMMA', the comma is actually a semicolon.)
831     io.HandleRelativePosition(byteCount);
832     ch = io.GetNextNonBlank(byteCount);
833   }
834   eatComma_ = true;
835   if (!ch) {
836     return Fortran::common::nullopt;
837   }
838   if (*ch == '/') {
839     hitSlash_ = true;
840     edit.descriptor = DataEdit::ListDirectedNullValue;
841     return edit;
842   }
843   if (*ch == comma) { // separator: null value
844     edit.descriptor = DataEdit::ListDirectedNullValue;
845     return edit;
846   }
847   if (imaginaryPart_) { // can't repeat components
848     return edit;
849   }
850   if (*ch >= '0' && *ch <= '9') { // look for "r*" repetition count
851     auto start{connection.positionInRecord};
852     int r{0};
853     do {
854       static auto constexpr clamp{(std::numeric_limits<int>::max() - '9') / 10};
855       if (r >= clamp) {
856         r = 0;
857         break;
858       }
859       r = 10 * r + (*ch - '0');
860       io.HandleRelativePosition(byteCount);
861       ch = io.GetCurrentChar(byteCount);
862     } while (ch && *ch >= '0' && *ch <= '9');
863     if (r > 0 && ch && *ch == '*') { // subtle: r must be nonzero
864       io.HandleRelativePosition(byteCount);
865       ch = io.GetCurrentChar(byteCount);
866       if (ch && *ch == '/') { // r*/
867         hitSlash_ = true;
868         edit.descriptor = DataEdit::ListDirectedNullValue;
869         return edit;
870       }
871       if (!ch || *ch == ' ' || *ch == '\t' || *ch == comma) { // "r*" null
872         edit.descriptor = DataEdit::ListDirectedNullValue;
873       }
874       edit.repeat = std::min<int>(r, maxRepeat);
875       remaining_ = r - edit.repeat;
876       if (remaining_ > 0) {
877         repeatPosition_.emplace(io);
878       }
879     } else { // not a repetition count, just an integer value; rewind
880       connection.positionInRecord = start;
881     }
882   }
883   if (!imaginaryPart_ && ch && *ch == '(') {
884     realPart_ = true;
885     io.HandleRelativePosition(byteCount);
886     edit.descriptor = DataEdit::ListDirectedRealPart;
887   }
888   return edit;
889 }
890 
891 template <Direction DIR>
892 int ExternalListIoStatementState<DIR>::EndIoStatement() {
893   if constexpr (DIR == Direction::Input) {
894     if (auto status{ListDirectedStatementState<DIR>::EndIoStatement()};
895         status != IostatOk) {
896       return status;
897     }
898   }
899   return ExternalIoStatementState<DIR>::EndIoStatement();
900 }
901 
902 template <Direction DIR>
903 bool ExternalUnformattedIoStatementState<DIR>::Receive(
904     char *data, std::size_t bytes, std::size_t elementBytes) {
905   if constexpr (DIR == Direction::Output) {
906     this->Crash("ExternalUnformattedIoStatementState::Receive() called for "
907                 "output statement");
908   }
909   return this->unit().Receive(data, bytes, elementBytes, *this);
910 }
911 
912 template <Direction DIR>
913 ChildIoStatementState<DIR>::ChildIoStatementState(
914     ChildIo &child, const char *sourceFile, int sourceLine)
915     : IoStatementBase{sourceFile, sourceLine}, child_{child} {}
916 
917 template <Direction DIR>
918 MutableModes &ChildIoStatementState<DIR>::mutableModes() {
919 #if !defined(RT_DEVICE_AVOID_RECURSION)
920   return child_.parent().mutableModes();
921 #else
922   ReportUnsupportedChildIo();
923 #endif
924 }
925 
926 template <Direction DIR>
927 ConnectionState &ChildIoStatementState<DIR>::GetConnectionState() {
928 #if !defined(RT_DEVICE_AVOID_RECURSION)
929   return child_.parent().GetConnectionState();
930 #else
931   ReportUnsupportedChildIo();
932 #endif
933 }
934 
935 template <Direction DIR>
936 ExternalFileUnit *ChildIoStatementState<DIR>::GetExternalFileUnit() const {
937 #if !defined(RT_DEVICE_AVOID_RECURSION)
938   return child_.parent().GetExternalFileUnit();
939 #else
940   ReportUnsupportedChildIo();
941 #endif
942 }
943 
944 template <Direction DIR> int ChildIoStatementState<DIR>::EndIoStatement() {
945   CompleteOperation();
946   auto result{IoStatementBase::EndIoStatement()};
947   child_.EndIoStatement(); // annihilates *this in child_.u_
948   return result;
949 }
950 
951 template <Direction DIR>
952 bool ChildIoStatementState<DIR>::Emit(
953     const char *data, std::size_t bytes, std::size_t elementBytes) {
954 #if !defined(RT_DEVICE_AVOID_RECURSION)
955   return child_.parent().Emit(data, bytes, elementBytes);
956 #else
957   ReportUnsupportedChildIo();
958 #endif
959 }
960 
961 template <Direction DIR>
962 std::size_t ChildIoStatementState<DIR>::GetNextInputBytes(const char *&p) {
963 #if !defined(RT_DEVICE_AVOID_RECURSION)
964   return child_.parent().GetNextInputBytes(p);
965 #else
966   ReportUnsupportedChildIo();
967 #endif
968 }
969 
970 template <Direction DIR>
971 void ChildIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) {
972 #if !defined(RT_DEVICE_AVOID_RECURSION)
973   return child_.parent().HandleAbsolutePosition(n);
974 #else
975   ReportUnsupportedChildIo();
976 #endif
977 }
978 
979 template <Direction DIR>
980 void ChildIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) {
981 #if !defined(RT_DEVICE_AVOID_RECURSION)
982   return child_.parent().HandleRelativePosition(n);
983 #else
984   ReportUnsupportedChildIo();
985 #endif
986 }
987 
988 template <Direction DIR, typename CHAR>
989 ChildFormattedIoStatementState<DIR, CHAR>::ChildFormattedIoStatementState(
990     ChildIo &child, const CHAR *format, std::size_t formatLength,
991     const Descriptor *formatDescriptor, const char *sourceFile, int sourceLine)
992     : ChildIoStatementState<DIR>{child, sourceFile, sourceLine},
993       mutableModes_{child.parent().mutableModes()}, format_{*this, format,
994                                                         formatLength,
995                                                         formatDescriptor} {}
996 
997 template <Direction DIR, typename CHAR>
998 void ChildFormattedIoStatementState<DIR, CHAR>::CompleteOperation() {
999   if (!this->completedOperation()) {
1000     format_.Finish(*this);
1001     ChildIoStatementState<DIR>::CompleteOperation();
1002   }
1003 }
1004 
1005 template <Direction DIR, typename CHAR>
1006 int ChildFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
1007   CompleteOperation();
1008   return ChildIoStatementState<DIR>::EndIoStatement();
1009 }
1010 
1011 template <Direction DIR, typename CHAR>
1012 bool ChildFormattedIoStatementState<DIR, CHAR>::AdvanceRecord(int n) {
1013 #if !defined(RT_DEVICE_AVOID_RECURSION)
1014   return this->child().parent().AdvanceRecord(n);
1015 #else
1016   this->ReportUnsupportedChildIo();
1017 #endif
1018 }
1019 
1020 template <Direction DIR>
1021 bool ChildUnformattedIoStatementState<DIR>::Receive(
1022     char *data, std::size_t bytes, std::size_t elementBytes) {
1023 #if !defined(RT_DEVICE_AVOID_RECURSION)
1024   return this->child().parent().Receive(data, bytes, elementBytes);
1025 #else
1026   this->ReportUnsupportedChildIo();
1027 #endif
1028 }
1029 
1030 template <Direction DIR> int ChildListIoStatementState<DIR>::EndIoStatement() {
1031   if constexpr (DIR == Direction::Input) {
1032     if (int status{ListDirectedStatementState<DIR>::EndIoStatement()};
1033         status != IostatOk) {
1034       return status;
1035     }
1036   }
1037   return ChildIoStatementState<DIR>::EndIoStatement();
1038 }
1039 
1040 template class InternalIoStatementState<Direction::Output>;
1041 template class InternalIoStatementState<Direction::Input>;
1042 template class InternalFormattedIoStatementState<Direction::Output>;
1043 template class InternalFormattedIoStatementState<Direction::Input>;
1044 template class InternalListIoStatementState<Direction::Output>;
1045 template class InternalListIoStatementState<Direction::Input>;
1046 template class ExternalIoStatementState<Direction::Output>;
1047 template class ExternalIoStatementState<Direction::Input>;
1048 template class ExternalFormattedIoStatementState<Direction::Output>;
1049 template class ExternalFormattedIoStatementState<Direction::Input>;
1050 template class ExternalListIoStatementState<Direction::Output>;
1051 template class ExternalListIoStatementState<Direction::Input>;
1052 template class ExternalUnformattedIoStatementState<Direction::Output>;
1053 template class ExternalUnformattedIoStatementState<Direction::Input>;
1054 template class ChildIoStatementState<Direction::Output>;
1055 template class ChildIoStatementState<Direction::Input>;
1056 template class ChildFormattedIoStatementState<Direction::Output>;
1057 template class ChildFormattedIoStatementState<Direction::Input>;
1058 template class ChildListIoStatementState<Direction::Output>;
1059 template class ChildListIoStatementState<Direction::Input>;
1060 template class ChildUnformattedIoStatementState<Direction::Output>;
1061 template class ChildUnformattedIoStatementState<Direction::Input>;
1062 
1063 void ExternalMiscIoStatementState::CompleteOperation() {
1064   if (completedOperation()) {
1065     return;
1066   }
1067   ExternalFileUnit &ext{unit()};
1068   switch (which_) {
1069   case Flush:
1070     ext.FlushOutput(*this);
1071 #if !defined(RT_DEVICE_COMPILATION)
1072     std::fflush(nullptr); // flushes C stdio output streams (12.9(2))
1073 #endif
1074     break;
1075   case Backspace:
1076     ext.BackspaceRecord(*this);
1077     break;
1078   case Endfile:
1079     ext.Endfile(*this);
1080     break;
1081   case Rewind:
1082     ext.Rewind(*this);
1083     break;
1084   case Wait:
1085     break; // handled in io-api.cpp BeginWait
1086   }
1087   return IoStatementBase::CompleteOperation();
1088 }
1089 
1090 int ExternalMiscIoStatementState::EndIoStatement() {
1091   CompleteOperation();
1092   return ExternalIoStatementBase::EndIoStatement();
1093 }
1094 
1095 InquireUnitState::InquireUnitState(
1096     ExternalFileUnit &unit, const char *sourceFile, int sourceLine)
1097     : ExternalIoStatementBase{unit, sourceFile, sourceLine} {}
1098 
1099 bool InquireUnitState::Inquire(
1100     InquiryKeywordHash inquiry, char *result, std::size_t length) {
1101   if (unit().createdForInternalChildIo()) {
1102     SignalError(IostatInquireInternalUnit,
1103         "INQUIRE of unit created for defined derived type I/O of an internal "
1104         "unit");
1105     return false;
1106   }
1107   const char *str{nullptr};
1108   switch (inquiry) {
1109   case HashInquiryKeyword("ACCESS"):
1110     if (!unit().IsConnected()) {
1111       str = "UNDEFINED";
1112     } else {
1113       switch (unit().access) {
1114       case Access::Sequential:
1115         str = "SEQUENTIAL";
1116         break;
1117       case Access::Direct:
1118         str = "DIRECT";
1119         break;
1120       case Access::Stream:
1121         str = "STREAM";
1122         break;
1123       }
1124     }
1125     break;
1126   case HashInquiryKeyword("ACTION"):
1127     str = !unit().IsConnected() ? "UNDEFINED"
1128         : unit().mayWrite()     ? unit().mayRead() ? "READWRITE" : "WRITE"
1129                                 : "READ";
1130     break;
1131   case HashInquiryKeyword("ASYNCHRONOUS"):
1132     str = !unit().IsConnected()    ? "UNDEFINED"
1133         : unit().mayAsynchronous() ? "YES"
1134                                    : "NO";
1135     break;
1136   case HashInquiryKeyword("BLANK"):
1137     str = !unit().IsConnected() || unit().isUnformatted.value_or(true)
1138         ? "UNDEFINED"
1139         : mutableModes().editingFlags & blankZero ? "ZERO"
1140                                                   : "NULL";
1141     break;
1142   case HashInquiryKeyword("CARRIAGECONTROL"):
1143     str = "LIST";
1144     break;
1145   case HashInquiryKeyword("CONVERT"):
1146     str = unit().swapEndianness() ? "SWAP" : "NATIVE";
1147     break;
1148   case HashInquiryKeyword("DECIMAL"):
1149     str = !unit().IsConnected() || unit().isUnformatted.value_or(true)
1150         ? "UNDEFINED"
1151         : mutableModes().editingFlags & decimalComma ? "COMMA"
1152                                                      : "POINT";
1153     break;
1154   case HashInquiryKeyword("DELIM"):
1155     if (!unit().IsConnected() || unit().isUnformatted.value_or(true)) {
1156       str = "UNDEFINED";
1157     } else {
1158       switch (mutableModes().delim) {
1159       case '\'':
1160         str = "APOSTROPHE";
1161         break;
1162       case '"':
1163         str = "QUOTE";
1164         break;
1165       default:
1166         str = "NONE";
1167         break;
1168       }
1169     }
1170     break;
1171   case HashInquiryKeyword("DIRECT"):
1172     str = !unit().IsConnected() ? "UNKNOWN"
1173         : unit().access == Access::Direct ||
1174             (unit().mayPosition() && unit().openRecl)
1175         ? "YES"
1176         : "NO";
1177     break;
1178   case HashInquiryKeyword("ENCODING"):
1179     str = !unit().IsConnected()               ? "UNKNOWN"
1180         : unit().isUnformatted.value_or(true) ? "UNDEFINED"
1181         : unit().isUTF8                       ? "UTF-8"
1182                                               : "ASCII";
1183     break;
1184   case HashInquiryKeyword("FORM"):
1185     str = !unit().IsConnected() || !unit().isUnformatted ? "UNDEFINED"
1186         : *unit().isUnformatted                          ? "UNFORMATTED"
1187                                                          : "FORMATTED";
1188     break;
1189   case HashInquiryKeyword("FORMATTED"):
1190     str = !unit().IsConnected() ? "UNDEFINED"
1191         : !unit().isUnformatted ? "UNKNOWN"
1192         : *unit().isUnformatted ? "NO"
1193                                 : "YES";
1194     break;
1195   case HashInquiryKeyword("NAME"):
1196     str = unit().path();
1197     if (!str) {
1198       return true; // result is undefined
1199     }
1200     break;
1201   case HashInquiryKeyword("PAD"):
1202     str = !unit().IsConnected() || unit().isUnformatted.value_or(true)
1203         ? "UNDEFINED"
1204         : mutableModes().pad ? "YES"
1205                              : "NO";
1206     break;
1207   case HashInquiryKeyword("POSITION"):
1208     if (!unit().IsConnected() || unit().access == Access::Direct) {
1209       str = "UNDEFINED";
1210     } else {
1211       switch (unit().InquirePosition()) {
1212       case Position::Rewind:
1213         str = "REWIND";
1214         break;
1215       case Position::Append:
1216         str = "APPEND";
1217         break;
1218       case Position::AsIs:
1219         str = "ASIS";
1220         break;
1221       }
1222     }
1223     break;
1224   case HashInquiryKeyword("READ"):
1225     str = !unit().IsConnected() ? "UNDEFINED" : unit().mayRead() ? "YES" : "NO";
1226     break;
1227   case HashInquiryKeyword("READWRITE"):
1228     str = !unit().IsConnected()                 ? "UNDEFINED"
1229         : unit().mayRead() && unit().mayWrite() ? "YES"
1230                                                 : "NO";
1231     break;
1232   case HashInquiryKeyword("ROUND"):
1233     if (!unit().IsConnected() || unit().isUnformatted.value_or(true)) {
1234       str = "UNDEFINED";
1235     } else {
1236       switch (mutableModes().round) {
1237       case decimal::FortranRounding::RoundNearest:
1238         str = "NEAREST";
1239         break;
1240       case decimal::FortranRounding::RoundUp:
1241         str = "UP";
1242         break;
1243       case decimal::FortranRounding::RoundDown:
1244         str = "DOWN";
1245         break;
1246       case decimal::FortranRounding::RoundToZero:
1247         str = "ZERO";
1248         break;
1249       case decimal::FortranRounding::RoundCompatible:
1250         str = "COMPATIBLE";
1251         break;
1252       }
1253     }
1254     break;
1255   case HashInquiryKeyword("SEQUENTIAL"):
1256     // "NO" for Direct, since Sequential would not work if
1257     // the unit were reopened without RECL=.
1258     str = !unit().IsConnected()               ? "UNKNOWN"
1259         : unit().access == Access::Sequential ? "YES"
1260                                               : "NO";
1261     break;
1262   case HashInquiryKeyword("SIGN"):
1263     str = !unit().IsConnected() || unit().isUnformatted.value_or(true)
1264         ? "UNDEFINED"
1265         : mutableModes().editingFlags & signPlus ? "PLUS"
1266                                                  : "SUPPRESS";
1267     break;
1268   case HashInquiryKeyword("STREAM"):
1269     str = !unit().IsConnected()           ? "UNKNOWN"
1270         : unit().access == Access::Stream ? "YES"
1271                                           : "NO";
1272     break;
1273   case HashInquiryKeyword("UNFORMATTED"):
1274     str = !unit().IsConnected() || !unit().isUnformatted ? "UNKNOWN"
1275         : *unit().isUnformatted                          ? "YES"
1276                                                          : "NO";
1277     break;
1278   case HashInquiryKeyword("WRITE"):
1279     str = !unit().IsConnected() ? "UNKNOWN" : unit().mayWrite() ? "YES" : "NO";
1280     break;
1281   }
1282   if (str) {
1283     ToFortranDefaultCharacter(result, length, str);
1284     return true;
1285   } else {
1286     BadInquiryKeywordHashCrash(inquiry);
1287     return false;
1288   }
1289 }
1290 
1291 bool InquireUnitState::Inquire(InquiryKeywordHash inquiry, bool &result) {
1292   switch (inquiry) {
1293   case HashInquiryKeyword("EXIST"):
1294     result = true;
1295     return true;
1296   case HashInquiryKeyword("NAMED"):
1297     result = unit().path() != nullptr;
1298     return true;
1299   case HashInquiryKeyword("OPENED"):
1300     result = unit().IsConnected();
1301     return true;
1302   case HashInquiryKeyword("PENDING"):
1303     result = false; // asynchronous I/O is not implemented
1304     return true;
1305   default:
1306     BadInquiryKeywordHashCrash(inquiry);
1307     return false;
1308   }
1309 }
1310 
1311 bool InquireUnitState::Inquire(
1312     InquiryKeywordHash inquiry, std::int64_t, bool &result) {
1313   switch (inquiry) {
1314   case HashInquiryKeyword("PENDING"):
1315     result = false; // asynchronous I/O is not implemented
1316     return true;
1317   default:
1318     BadInquiryKeywordHashCrash(inquiry);
1319     return false;
1320   }
1321 }
1322 
1323 bool InquireUnitState::Inquire(
1324     InquiryKeywordHash inquiry, std::int64_t &result) {
1325   switch (inquiry) {
1326   case HashInquiryKeyword("NEXTREC"):
1327     if (unit().access == Access::Direct) {
1328       result = unit().currentRecordNumber;
1329     }
1330     return true;
1331   case HashInquiryKeyword("NUMBER"):
1332     result = unit().unitNumber();
1333     return true;
1334   case HashInquiryKeyword("POS"):
1335     result = unit().InquirePos();
1336     return true;
1337   case HashInquiryKeyword("RECL"):
1338     if (!unit().IsConnected()) {
1339       result = -1;
1340     } else if (unit().access == Access::Stream) {
1341       result = -2;
1342     } else if (unit().openRecl) {
1343       result = *unit().openRecl;
1344     } else {
1345       result = std::numeric_limits<std::int32_t>::max();
1346     }
1347     return true;
1348   case HashInquiryKeyword("SIZE"):
1349     result = -1;
1350     if (unit().IsConnected()) {
1351       unit().FlushOutput(*this);
1352       if (auto size{unit().knownSize()}) {
1353         result = *size;
1354       }
1355     }
1356     return true;
1357   default:
1358     BadInquiryKeywordHashCrash(inquiry);
1359     return false;
1360   }
1361 }
1362 
1363 InquireNoUnitState::InquireNoUnitState(
1364     const char *sourceFile, int sourceLine, int badUnitNumber)
1365     : NoUnitIoStatementState{*this, sourceFile, sourceLine, badUnitNumber} {}
1366 
1367 bool InquireNoUnitState::Inquire(
1368     InquiryKeywordHash inquiry, char *result, std::size_t length) {
1369   switch (inquiry) {
1370   case HashInquiryKeyword("ACCESS"):
1371   case HashInquiryKeyword("ACTION"):
1372   case HashInquiryKeyword("ASYNCHRONOUS"):
1373   case HashInquiryKeyword("BLANK"):
1374   case HashInquiryKeyword("CARRIAGECONTROL"):
1375   case HashInquiryKeyword("CONVERT"):
1376   case HashInquiryKeyword("DECIMAL"):
1377   case HashInquiryKeyword("DELIM"):
1378   case HashInquiryKeyword("FORM"):
1379   case HashInquiryKeyword("NAME"):
1380   case HashInquiryKeyword("PAD"):
1381   case HashInquiryKeyword("POSITION"):
1382   case HashInquiryKeyword("ROUND"):
1383   case HashInquiryKeyword("SIGN"):
1384     ToFortranDefaultCharacter(result, length, "UNDEFINED");
1385     return true;
1386   case HashInquiryKeyword("DIRECT"):
1387   case HashInquiryKeyword("ENCODING"):
1388   case HashInquiryKeyword("FORMATTED"):
1389   case HashInquiryKeyword("READ"):
1390   case HashInquiryKeyword("READWRITE"):
1391   case HashInquiryKeyword("SEQUENTIAL"):
1392   case HashInquiryKeyword("STREAM"):
1393   case HashInquiryKeyword("WRITE"):
1394   case HashInquiryKeyword("UNFORMATTED"):
1395     ToFortranDefaultCharacter(result, length, "UNKNOWN");
1396     return true;
1397   default:
1398     BadInquiryKeywordHashCrash(inquiry);
1399     return false;
1400   }
1401 }
1402 
1403 bool InquireNoUnitState::Inquire(InquiryKeywordHash inquiry, bool &result) {
1404   switch (inquiry) {
1405   case HashInquiryKeyword("EXIST"):
1406     result = badUnitNumber() >= 0;
1407     return true;
1408   case HashInquiryKeyword("NAMED"):
1409   case HashInquiryKeyword("OPENED"):
1410   case HashInquiryKeyword("PENDING"):
1411     result = false;
1412     return true;
1413   default:
1414     BadInquiryKeywordHashCrash(inquiry);
1415     return false;
1416   }
1417 }
1418 
1419 bool InquireNoUnitState::Inquire(
1420     InquiryKeywordHash inquiry, std::int64_t, bool &result) {
1421   switch (inquiry) {
1422   case HashInquiryKeyword("PENDING"):
1423     result = false;
1424     return true;
1425   default:
1426     BadInquiryKeywordHashCrash(inquiry);
1427     return false;
1428   }
1429 }
1430 
1431 bool InquireNoUnitState::Inquire(
1432     InquiryKeywordHash inquiry, std::int64_t &result) {
1433   switch (inquiry) {
1434   case HashInquiryKeyword("NUMBER"):
1435     result = badUnitNumber();
1436     return true;
1437   case HashInquiryKeyword("NEXTREC"):
1438   case HashInquiryKeyword("POS"):
1439   case HashInquiryKeyword("RECL"):
1440   case HashInquiryKeyword("SIZE"):
1441     result = -1;
1442     return true;
1443   default:
1444     BadInquiryKeywordHashCrash(inquiry);
1445     return false;
1446   }
1447 }
1448 
1449 InquireUnconnectedFileState::InquireUnconnectedFileState(
1450     OwningPtr<char> &&path, const char *sourceFile, int sourceLine)
1451     : NoUnitIoStatementState{*this, sourceFile, sourceLine}, path_{std::move(
1452                                                                  path)} {}
1453 
1454 bool InquireUnconnectedFileState::Inquire(
1455     InquiryKeywordHash inquiry, char *result, std::size_t length) {
1456   const char *str{nullptr};
1457   switch (inquiry) {
1458   case HashInquiryKeyword("ACCESS"):
1459   case HashInquiryKeyword("ACTION"):
1460   case HashInquiryKeyword("ASYNCHRONOUS"):
1461   case HashInquiryKeyword("BLANK"):
1462   case HashInquiryKeyword("CARRIAGECONTROL"):
1463   case HashInquiryKeyword("CONVERT"):
1464   case HashInquiryKeyword("DECIMAL"):
1465   case HashInquiryKeyword("DELIM"):
1466   case HashInquiryKeyword("FORM"):
1467   case HashInquiryKeyword("PAD"):
1468   case HashInquiryKeyword("POSITION"):
1469   case HashInquiryKeyword("ROUND"):
1470   case HashInquiryKeyword("SIGN"):
1471     str = "UNDEFINED";
1472     break;
1473   case HashInquiryKeyword("DIRECT"):
1474   case HashInquiryKeyword("ENCODING"):
1475   case HashInquiryKeyword("FORMATTED"):
1476   case HashInquiryKeyword("SEQUENTIAL"):
1477   case HashInquiryKeyword("STREAM"):
1478   case HashInquiryKeyword("UNFORMATTED"):
1479     str = "UNKNOWN";
1480     break;
1481   case HashInquiryKeyword("READ"):
1482     str =
1483         IsExtant(path_.get()) ? MayRead(path_.get()) ? "YES" : "NO" : "UNKNOWN";
1484     break;
1485   case HashInquiryKeyword("READWRITE"):
1486     str = IsExtant(path_.get()) ? MayReadAndWrite(path_.get()) ? "YES" : "NO"
1487                                 : "UNKNOWN";
1488     break;
1489   case HashInquiryKeyword("WRITE"):
1490     str = IsExtant(path_.get()) ? MayWrite(path_.get()) ? "YES" : "NO"
1491                                 : "UNKNOWN";
1492     break;
1493   case HashInquiryKeyword("NAME"):
1494     str = path_.get();
1495     if (!str) {
1496       return true; // result is undefined
1497     }
1498     break;
1499   }
1500   if (str) {
1501     ToFortranDefaultCharacter(result, length, str);
1502     return true;
1503   } else {
1504     BadInquiryKeywordHashCrash(inquiry);
1505     return false;
1506   }
1507 }
1508 
1509 bool InquireUnconnectedFileState::Inquire(
1510     InquiryKeywordHash inquiry, bool &result) {
1511   switch (inquiry) {
1512   case HashInquiryKeyword("EXIST"):
1513     result = IsExtant(path_.get());
1514     return true;
1515   case HashInquiryKeyword("NAMED"):
1516     result = true;
1517     return true;
1518   case HashInquiryKeyword("OPENED"):
1519     result = false;
1520     return true;
1521   case HashInquiryKeyword("PENDING"):
1522     result = false;
1523     return true;
1524   default:
1525     BadInquiryKeywordHashCrash(inquiry);
1526     return false;
1527   }
1528 }
1529 
1530 bool InquireUnconnectedFileState::Inquire(
1531     InquiryKeywordHash inquiry, std::int64_t, bool &result) {
1532   switch (inquiry) {
1533   case HashInquiryKeyword("PENDING"):
1534     result = false;
1535     return true;
1536   default:
1537     BadInquiryKeywordHashCrash(inquiry);
1538     return false;
1539   }
1540 }
1541 
1542 bool InquireUnconnectedFileState::Inquire(
1543     InquiryKeywordHash inquiry, std::int64_t &result) {
1544   switch (inquiry) {
1545   case HashInquiryKeyword("NEXTREC"):
1546   case HashInquiryKeyword("NUMBER"):
1547   case HashInquiryKeyword("POS"):
1548   case HashInquiryKeyword("RECL"):
1549     result = -1;
1550     return true;
1551   case HashInquiryKeyword("SIZE"):
1552     result = SizeInBytes(path_.get());
1553     return true;
1554   default:
1555     BadInquiryKeywordHashCrash(inquiry);
1556     return false;
1557   }
1558 }
1559 
1560 InquireIOLengthState::InquireIOLengthState(
1561     const char *sourceFile, int sourceLine)
1562     : NoUnitIoStatementState{*this, sourceFile, sourceLine} {}
1563 
1564 bool InquireIOLengthState::Emit(const char *, std::size_t bytes, std::size_t) {
1565   bytes_ += bytes;
1566   return true;
1567 }
1568 
1569 int ErroneousIoStatementState::EndIoStatement() {
1570   SignalPendingError();
1571   if (unit_) {
1572     unit_->EndIoStatement();
1573   }
1574   return IoStatementBase::EndIoStatement();
1575 }
1576 
1577 RT_OFFLOAD_API_GROUP_END
1578 } // namespace Fortran::runtime::io
1579