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