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