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