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