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