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