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("CONVERT"):
783     str = unit().swapEndianness() ? "SWAP" : "NATIVE";
784     break;
785   case HashInquiryKeyword("DECIMAL"):
786     str = unit().isUnformatted                     ? "UNDEFINED"
787         : unit().modes.editingFlags & decimalComma ? "COMMA"
788                                                    : "POINT";
789     break;
790   case HashInquiryKeyword("DELIM"):
791     if (unit().isUnformatted) {
792       str = "UNDEFINED";
793     } else {
794       switch (unit().modes.delim) {
795       case '\'':
796         str = "APOSTROPHE";
797         break;
798       case '"':
799         str = "QUOTE";
800         break;
801       default:
802         str = "NONE";
803         break;
804       }
805     }
806     break;
807   case HashInquiryKeyword("DIRECT"):
808     str = unit().mayPosition() ? "YES" : "NO";
809     break;
810   case HashInquiryKeyword("ENCODING"):
811     str = unit().isUnformatted ? "UNDEFINED"
812         : unit().isUTF8        ? "UTF-8"
813                                : "ASCII";
814     break;
815   case HashInquiryKeyword("FORM"):
816     str = unit().isUnformatted ? "UNFORMATTED" : "FORMATTED";
817     break;
818   case HashInquiryKeyword("FORMATTED"):
819     str = "YES";
820     break;
821   case HashInquiryKeyword("NAME"):
822     str = unit().path();
823     if (!str) {
824       return true; // result is undefined
825     }
826     break;
827   case HashInquiryKeyword("PAD"):
828     str = unit().isUnformatted ? "UNDEFINED" : unit().modes.pad ? "YES" : "NO";
829     break;
830   case HashInquiryKeyword("POSITION"):
831     if (unit().access == Access::Direct) {
832       str = "UNDEFINED";
833     } else {
834       auto size{unit().knownSize()};
835       auto pos{unit().position()};
836       if (pos == size.value_or(pos + 1)) {
837         str = "APPEND";
838       } else if (pos == 0) {
839         str = "REWIND";
840       } else {
841         str = "ASIS"; // processor-dependent & no common behavior
842       }
843     }
844     break;
845   case HashInquiryKeyword("READ"):
846     str = unit().mayRead() ? "YES" : "NO";
847     break;
848   case HashInquiryKeyword("READWRITE"):
849     str = unit().mayRead() && unit().mayWrite() ? "YES" : "NO";
850     break;
851   case HashInquiryKeyword("ROUND"):
852     if (unit().isUnformatted) {
853       str = "UNDEFINED";
854     } else {
855       switch (unit().modes.round) {
856       case decimal::FortranRounding::RoundNearest:
857         str = "NEAREST";
858         break;
859       case decimal::FortranRounding::RoundUp:
860         str = "UP";
861         break;
862       case decimal::FortranRounding::RoundDown:
863         str = "DOWN";
864         break;
865       case decimal::FortranRounding::RoundToZero:
866         str = "ZERO";
867         break;
868       case decimal::FortranRounding::RoundCompatible:
869         str = "COMPATIBLE";
870         break;
871       }
872     }
873     break;
874   case HashInquiryKeyword("SEQUENTIAL"):
875     str = "YES";
876     break;
877   case HashInquiryKeyword("SIGN"):
878     str = unit().isUnformatted                 ? "UNDEFINED"
879         : unit().modes.editingFlags & signPlus ? "PLUS"
880                                                : "SUPPRESS";
881     break;
882   case HashInquiryKeyword("STREAM"):
883     str = "YES";
884     break;
885   case HashInquiryKeyword("WRITE"):
886     str = unit().mayWrite() ? "YES" : "NO";
887     break;
888   case HashInquiryKeyword("UNFORMATTED"):
889     str = "YES";
890     break;
891   }
892   if (str) {
893     ToFortranDefaultCharacter(result, length, str);
894     return true;
895   } else {
896     BadInquiryKeywordHashCrash(inquiry);
897     return false;
898   }
899 }
900 
901 bool InquireUnitState::Inquire(InquiryKeywordHash inquiry, bool &result) {
902   switch (inquiry) {
903   case HashInquiryKeyword("EXIST"):
904     result = true;
905     return true;
906   case HashInquiryKeyword("NAMED"):
907     result = unit().path() != nullptr;
908     return true;
909   case HashInquiryKeyword("OPENED"):
910     result = true;
911     return true;
912   case HashInquiryKeyword("PENDING"):
913     result = false; // asynchronous I/O is not implemented
914     return true;
915   default:
916     BadInquiryKeywordHashCrash(inquiry);
917     return false;
918   }
919 }
920 
921 bool InquireUnitState::Inquire(
922     InquiryKeywordHash inquiry, std::int64_t, bool &result) {
923   switch (inquiry) {
924   case HashInquiryKeyword("PENDING"):
925     result = false; // asynchronous I/O is not implemented
926     return true;
927   default:
928     BadInquiryKeywordHashCrash(inquiry);
929     return false;
930   }
931 }
932 
933 bool InquireUnitState::Inquire(
934     InquiryKeywordHash inquiry, std::int64_t &result) {
935   switch (inquiry) {
936   case HashInquiryKeyword("NEXTREC"):
937     if (unit().access == Access::Direct) {
938       result = unit().currentRecordNumber;
939     }
940     return true;
941   case HashInquiryKeyword("NUMBER"):
942     result = unit().unitNumber();
943     return true;
944   case HashInquiryKeyword("POS"):
945     result = unit().position();
946     return true;
947   case HashInquiryKeyword("RECL"):
948     if (unit().access == Access::Stream) {
949       result = -2;
950     } else if (unit().isFixedRecordLength && unit().recordLength) {
951       result = *unit().recordLength;
952     } else {
953       result = std::numeric_limits<std::uint32_t>::max();
954     }
955     return true;
956   case HashInquiryKeyword("SIZE"):
957     if (auto size{unit().knownSize()}) {
958       result = *size;
959     } else {
960       result = -1;
961     }
962     return true;
963   default:
964     BadInquiryKeywordHashCrash(inquiry);
965     return false;
966   }
967 }
968 
969 InquireNoUnitState::InquireNoUnitState(const char *sourceFile, int sourceLine)
970     : NoUnitIoStatementState{sourceFile, sourceLine, *this} {}
971 
972 bool InquireNoUnitState::Inquire(
973     InquiryKeywordHash inquiry, char *result, std::size_t length) {
974   switch (inquiry) {
975   case HashInquiryKeyword("ACCESS"):
976   case HashInquiryKeyword("ACTION"):
977   case HashInquiryKeyword("ASYNCHRONOUS"):
978   case HashInquiryKeyword("BLANK"):
979   case HashInquiryKeyword("CONVERT"):
980   case HashInquiryKeyword("DECIMAL"):
981   case HashInquiryKeyword("DELIM"):
982   case HashInquiryKeyword("FORM"):
983   case HashInquiryKeyword("NAME"):
984   case HashInquiryKeyword("PAD"):
985   case HashInquiryKeyword("POSITION"):
986   case HashInquiryKeyword("ROUND"):
987   case HashInquiryKeyword("SIGN"):
988     ToFortranDefaultCharacter(result, length, "UNDEFINED");
989     return true;
990   case HashInquiryKeyword("DIRECT"):
991   case HashInquiryKeyword("ENCODING"):
992   case HashInquiryKeyword("FORMATTED"):
993   case HashInquiryKeyword("READ"):
994   case HashInquiryKeyword("READWRITE"):
995   case HashInquiryKeyword("SEQUENTIAL"):
996   case HashInquiryKeyword("STREAM"):
997   case HashInquiryKeyword("WRITE"):
998   case HashInquiryKeyword("UNFORMATTED"):
999     ToFortranDefaultCharacter(result, length, "UNKNONN");
1000     return true;
1001   default:
1002     BadInquiryKeywordHashCrash(inquiry);
1003     return false;
1004   }
1005 }
1006 
1007 bool InquireNoUnitState::Inquire(InquiryKeywordHash inquiry, bool &result) {
1008   switch (inquiry) {
1009   case HashInquiryKeyword("EXIST"):
1010     result = true;
1011     return true;
1012   case HashInquiryKeyword("NAMED"):
1013   case HashInquiryKeyword("OPENED"):
1014   case HashInquiryKeyword("PENDING"):
1015     result = false;
1016     return true;
1017   default:
1018     BadInquiryKeywordHashCrash(inquiry);
1019     return false;
1020   }
1021 }
1022 
1023 bool InquireNoUnitState::Inquire(
1024     InquiryKeywordHash inquiry, std::int64_t, bool &result) {
1025   switch (inquiry) {
1026   case HashInquiryKeyword("PENDING"):
1027     result = false;
1028     return true;
1029   default:
1030     BadInquiryKeywordHashCrash(inquiry);
1031     return false;
1032   }
1033 }
1034 
1035 bool InquireNoUnitState::Inquire(
1036     InquiryKeywordHash inquiry, std::int64_t &result) {
1037   switch (inquiry) {
1038   case HashInquiryKeyword("NEXTREC"):
1039   case HashInquiryKeyword("NUMBER"):
1040   case HashInquiryKeyword("POS"):
1041   case HashInquiryKeyword("RECL"):
1042   case HashInquiryKeyword("SIZE"):
1043     result = -1;
1044     return true;
1045   default:
1046     BadInquiryKeywordHashCrash(inquiry);
1047     return false;
1048   }
1049 }
1050 
1051 InquireUnconnectedFileState::InquireUnconnectedFileState(
1052     OwningPtr<char> &&path, const char *sourceFile, int sourceLine)
1053     : NoUnitIoStatementState{sourceFile, sourceLine, *this}, path_{std::move(
1054                                                                  path)} {}
1055 
1056 bool InquireUnconnectedFileState::Inquire(
1057     InquiryKeywordHash inquiry, char *result, std::size_t length) {
1058   const char *str{nullptr};
1059   switch (inquiry) {
1060   case HashInquiryKeyword("ACCESS"):
1061   case HashInquiryKeyword("ACTION"):
1062   case HashInquiryKeyword("ASYNCHRONOUS"):
1063   case HashInquiryKeyword("BLANK"):
1064   case HashInquiryKeyword("CONVERT"):
1065   case HashInquiryKeyword("DECIMAL"):
1066   case HashInquiryKeyword("DELIM"):
1067   case HashInquiryKeyword("FORM"):
1068   case HashInquiryKeyword("PAD"):
1069   case HashInquiryKeyword("POSITION"):
1070   case HashInquiryKeyword("ROUND"):
1071   case HashInquiryKeyword("SIGN"):
1072     str = "UNDEFINED";
1073     break;
1074   case HashInquiryKeyword("DIRECT"):
1075   case HashInquiryKeyword("ENCODING"):
1076     str = "UNKNONN";
1077     break;
1078   case HashInquiryKeyword("READ"):
1079     str = MayRead(path_.get()) ? "YES" : "NO";
1080     break;
1081   case HashInquiryKeyword("READWRITE"):
1082     str = MayReadAndWrite(path_.get()) ? "YES" : "NO";
1083     break;
1084   case HashInquiryKeyword("WRITE"):
1085     str = MayWrite(path_.get()) ? "YES" : "NO";
1086     break;
1087   case HashInquiryKeyword("FORMATTED"):
1088   case HashInquiryKeyword("SEQUENTIAL"):
1089   case HashInquiryKeyword("STREAM"):
1090   case HashInquiryKeyword("UNFORMATTED"):
1091     str = "YES";
1092     break;
1093   case HashInquiryKeyword("NAME"):
1094     str = path_.get();
1095     return true;
1096   }
1097   if (str) {
1098     ToFortranDefaultCharacter(result, length, str);
1099     return true;
1100   } else {
1101     BadInquiryKeywordHashCrash(inquiry);
1102     return false;
1103   }
1104 }
1105 
1106 bool InquireUnconnectedFileState::Inquire(
1107     InquiryKeywordHash inquiry, bool &result) {
1108   switch (inquiry) {
1109   case HashInquiryKeyword("EXIST"):
1110     result = IsExtant(path_.get());
1111     return true;
1112   case HashInquiryKeyword("NAMED"):
1113     result = true;
1114     return true;
1115   case HashInquiryKeyword("OPENED"):
1116     result = false;
1117     return true;
1118   case HashInquiryKeyword("PENDING"):
1119     result = false;
1120     return true;
1121   default:
1122     BadInquiryKeywordHashCrash(inquiry);
1123     return false;
1124   }
1125 }
1126 
1127 bool InquireUnconnectedFileState::Inquire(
1128     InquiryKeywordHash inquiry, std::int64_t, bool &result) {
1129   switch (inquiry) {
1130   case HashInquiryKeyword("PENDING"):
1131     result = false;
1132     return true;
1133   default:
1134     BadInquiryKeywordHashCrash(inquiry);
1135     return false;
1136   }
1137 }
1138 
1139 bool InquireUnconnectedFileState::Inquire(
1140     InquiryKeywordHash inquiry, std::int64_t &result) {
1141   switch (inquiry) {
1142   case HashInquiryKeyword("NEXTREC"):
1143   case HashInquiryKeyword("NUMBER"):
1144   case HashInquiryKeyword("POS"):
1145   case HashInquiryKeyword("RECL"):
1146   case HashInquiryKeyword("SIZE"):
1147     result = -1;
1148     return true;
1149   default:
1150     BadInquiryKeywordHashCrash(inquiry);
1151     return false;
1152   }
1153 }
1154 
1155 InquireIOLengthState::InquireIOLengthState(
1156     const char *sourceFile, int sourceLine)
1157     : NoUnitIoStatementState{sourceFile, sourceLine, *this} {}
1158 
1159 bool InquireIOLengthState::Emit(
1160     const char *, std::size_t n, std::size_t /*elementBytes*/) {
1161   bytes_ += n;
1162   return true;
1163 }
1164 
1165 } // namespace Fortran::runtime::io
1166