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