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