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