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