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 template <Direction DIR, typename CHAR>
30 InternalIoStatementState<DIR, CHAR>::InternalIoStatementState(
31     Buffer scalar, std::size_t length, const char *sourceFile, int sourceLine)
32     : IoStatementBase{sourceFile, sourceLine}, unit_{scalar, length} {}
33 
34 template <Direction DIR, typename CHAR>
35 InternalIoStatementState<DIR, CHAR>::InternalIoStatementState(
36     const Descriptor &d, const char *sourceFile, int sourceLine)
37     : IoStatementBase{sourceFile, sourceLine}, unit_{d, *this} {}
38 
39 template <Direction DIR, typename CHAR>
40 bool InternalIoStatementState<DIR, CHAR>::Emit(
41     const CharType *data, std::size_t chars, std::size_t /*elementBytes*/) {
42   if constexpr (DIR == Direction::Input) {
43     Crash("InternalIoStatementState<Direction::Input>::Emit() called");
44     return false;
45   }
46   return unit_.Emit(data, chars, *this);
47 }
48 
49 template <Direction DIR, typename CHAR>
50 std::optional<char32_t> InternalIoStatementState<DIR, CHAR>::GetCurrentChar() {
51   if constexpr (DIR == Direction::Output) {
52     Crash(
53         "InternalIoStatementState<Direction::Output>::GetCurrentChar() called");
54     return std::nullopt;
55   }
56   return unit_.GetCurrentChar(*this);
57 }
58 
59 template <Direction DIR, typename CHAR>
60 bool InternalIoStatementState<DIR, CHAR>::AdvanceRecord(int n) {
61   while (n-- > 0) {
62     if (!unit_.AdvanceRecord(*this)) {
63       return false;
64     }
65   }
66   return true;
67 }
68 
69 template <Direction DIR, typename CHAR>
70 void InternalIoStatementState<DIR, CHAR>::BackspaceRecord() {
71   unit_.BackspaceRecord(*this);
72 }
73 
74 template <Direction DIR, typename CHAR>
75 int InternalIoStatementState<DIR, CHAR>::EndIoStatement() {
76   if constexpr (DIR == Direction::Output) {
77     unit_.EndIoStatement(); // fill
78   }
79   auto result{IoStatementBase::EndIoStatement()};
80   if (free_) {
81     FreeMemory(this);
82   }
83   return result;
84 }
85 
86 template <Direction DIR, typename CHAR>
87 void InternalIoStatementState<DIR, CHAR>::HandleAbsolutePosition(
88     std::int64_t n) {
89   return unit_.HandleAbsolutePosition(n);
90 }
91 
92 template <Direction DIR, typename CHAR>
93 void InternalIoStatementState<DIR, CHAR>::HandleRelativePosition(
94     std::int64_t n) {
95   return unit_.HandleRelativePosition(n);
96 }
97 
98 template <Direction DIR, typename CHAR>
99 InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState(
100     Buffer buffer, std::size_t length, const CHAR *format,
101     std::size_t formatLength, const char *sourceFile, int sourceLine)
102     : InternalIoStatementState<DIR, CHAR>{buffer, length, sourceFile,
103           sourceLine},
104       ioStatementState_{*this}, format_{*this, format, formatLength} {}
105 
106 template <Direction DIR, typename CHAR>
107 InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState(
108     const Descriptor &d, const CHAR *format, std::size_t formatLength,
109     const char *sourceFile, int sourceLine)
110     : InternalIoStatementState<DIR, CHAR>{d, sourceFile, sourceLine},
111       ioStatementState_{*this}, format_{*this, format, formatLength} {}
112 
113 template <Direction DIR, typename CHAR>
114 int InternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
115   if constexpr (DIR == Direction::Output) {
116     format_.Finish(*this); // ignore any remaining input positioning actions
117   }
118   return InternalIoStatementState<DIR, CHAR>::EndIoStatement();
119 }
120 
121 template <Direction DIR, typename CHAR>
122 InternalListIoStatementState<DIR, CHAR>::InternalListIoStatementState(
123     Buffer buffer, std::size_t length, const char *sourceFile, int sourceLine)
124     : InternalIoStatementState<DIR, CharType>{buffer, length, sourceFile,
125           sourceLine},
126       ioStatementState_{*this} {}
127 
128 template <Direction DIR, typename CHAR>
129 InternalListIoStatementState<DIR, CHAR>::InternalListIoStatementState(
130     const Descriptor &d, const char *sourceFile, int sourceLine)
131     : InternalIoStatementState<DIR, CharType>{d, sourceFile, sourceLine},
132       ioStatementState_{*this} {}
133 
134 ExternalIoStatementBase::ExternalIoStatementBase(
135     ExternalFileUnit &unit, const char *sourceFile, int sourceLine)
136     : IoStatementBase{sourceFile, sourceLine}, unit_{unit} {}
137 
138 MutableModes &ExternalIoStatementBase::mutableModes() { return unit_.modes; }
139 
140 ConnectionState &ExternalIoStatementBase::GetConnectionState() { return unit_; }
141 
142 int ExternalIoStatementBase::EndIoStatement() {
143   if (unit_.nonAdvancing) {
144     unit_.leftTabLimit = unit_.furthestPositionInRecord;
145     unit_.nonAdvancing = false;
146   } else {
147     unit_.leftTabLimit.reset();
148   }
149   auto result{IoStatementBase::EndIoStatement()};
150   unit_.EndIoStatement(); // annihilates *this in unit_.u_
151   return result;
152 }
153 
154 void OpenStatementState::set_path(
155     const char *path, std::size_t length, int kind) {
156   if (kind != 1) { // TODO
157     Crash("OPEN: FILE= with unimplemented: CHARACTER(KIND=%d)", kind);
158   }
159   std::size_t bytes{length * kind}; // TODO: UTF-8 encoding of Unicode path
160   path_ = SaveDefaultCharacter(path, bytes, *this);
161   pathLength_ = length;
162 }
163 
164 int OpenStatementState::EndIoStatement() {
165   if (wasExtant_ && status_ && *status_ != OpenStatus::Old) {
166     SignalError("OPEN statement for connected unit may not have STATUS= other "
167                 "than 'OLD'");
168   }
169   unit().OpenUnit(status_.value_or(OpenStatus::Unknown), action_, position_,
170       std::move(path_), pathLength_, convert_, *this);
171   return ExternalIoStatementBase::EndIoStatement();
172 }
173 
174 int CloseStatementState::EndIoStatement() {
175   int result{ExternalIoStatementBase::EndIoStatement()};
176   unit().CloseUnit(status_, *this);
177   unit().DestroyClosed();
178   return result;
179 }
180 
181 int NoopCloseStatementState::EndIoStatement() {
182   auto result{IoStatementBase::EndIoStatement()};
183   FreeMemory(this);
184   return result;
185 }
186 
187 template <Direction DIR> int ExternalIoStatementState<DIR>::EndIoStatement() {
188   if (!unit().nonAdvancing) {
189     unit().AdvanceRecord(*this);
190   }
191   if constexpr (DIR == Direction::Output) {
192     unit().FlushIfTerminal(*this);
193   }
194   return ExternalIoStatementBase::EndIoStatement();
195 }
196 
197 template <Direction DIR>
198 bool ExternalIoStatementState<DIR>::Emit(
199     const char *data, std::size_t bytes, std::size_t elementBytes) {
200   if constexpr (DIR == Direction::Input) {
201     Crash("ExternalIoStatementState::Emit(char) called for input statement");
202   }
203   return unit().Emit(data, bytes, elementBytes, *this);
204 }
205 
206 template <Direction DIR>
207 bool ExternalIoStatementState<DIR>::Emit(
208     const char16_t *data, std::size_t chars) {
209   if constexpr (DIR == Direction::Input) {
210     Crash(
211         "ExternalIoStatementState::Emit(char16_t) called for input statement");
212   }
213   // TODO: UTF-8 encoding
214   return unit().Emit(reinterpret_cast<const char *>(data), chars * sizeof *data,
215       static_cast<int>(sizeof *data), *this);
216 }
217 
218 template <Direction DIR>
219 bool ExternalIoStatementState<DIR>::Emit(
220     const char32_t *data, std::size_t chars) {
221   if constexpr (DIR == Direction::Input) {
222     Crash(
223         "ExternalIoStatementState::Emit(char32_t) called for input statement");
224   }
225   // TODO: UTF-8 encoding
226   return unit().Emit(reinterpret_cast<const char *>(data), chars * sizeof *data,
227       static_cast<int>(sizeof *data), *this);
228 }
229 
230 template <Direction DIR>
231 std::optional<char32_t> ExternalIoStatementState<DIR>::GetCurrentChar() {
232   if constexpr (DIR == Direction::Output) {
233     Crash(
234         "ExternalIoStatementState<Direction::Output>::GetCurrentChar() called");
235   }
236   return unit().GetCurrentChar(*this);
237 }
238 
239 template <Direction DIR>
240 bool ExternalIoStatementState<DIR>::AdvanceRecord(int n) {
241   while (n-- > 0) {
242     if (!unit().AdvanceRecord(*this)) {
243       return false;
244     }
245   }
246   return true;
247 }
248 
249 template <Direction DIR> void ExternalIoStatementState<DIR>::BackspaceRecord() {
250   unit().BackspaceRecord(*this);
251 }
252 
253 template <Direction DIR>
254 void ExternalIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) {
255   return unit().HandleAbsolutePosition(n);
256 }
257 
258 template <Direction DIR>
259 void ExternalIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) {
260   return unit().HandleRelativePosition(n);
261 }
262 
263 template <Direction DIR, typename CHAR>
264 ExternalFormattedIoStatementState<DIR, CHAR>::ExternalFormattedIoStatementState(
265     ExternalFileUnit &unit, const CHAR *format, std::size_t formatLength,
266     const char *sourceFile, int sourceLine)
267     : ExternalIoStatementState<DIR>{unit, sourceFile, sourceLine},
268       mutableModes_{unit.modes}, format_{*this, format, formatLength} {}
269 
270 template <Direction DIR, typename CHAR>
271 int ExternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
272   format_.Finish(*this);
273   return ExternalIoStatementState<DIR>::EndIoStatement();
274 }
275 
276 std::optional<DataEdit> IoStatementState::GetNextDataEdit(int n) {
277   return std::visit(
278       [&](auto &x) { return x.get().GetNextDataEdit(*this, n); }, u_);
279 }
280 
281 bool IoStatementState::Emit(
282     const char *data, std::size_t n, std::size_t elementBytes) {
283   return std::visit(
284       [=](auto &x) { return x.get().Emit(data, n, elementBytes); }, u_);
285 }
286 
287 std::optional<char32_t> IoStatementState::GetCurrentChar() {
288   return std::visit([&](auto &x) { return x.get().GetCurrentChar(); }, u_);
289 }
290 
291 bool IoStatementState::AdvanceRecord(int n) {
292   return std::visit([=](auto &x) { return x.get().AdvanceRecord(n); }, u_);
293 }
294 
295 void IoStatementState::BackspaceRecord() {
296   std::visit([](auto &x) { x.get().BackspaceRecord(); }, u_);
297 }
298 
299 void IoStatementState::HandleRelativePosition(std::int64_t n) {
300   std::visit([=](auto &x) { x.get().HandleRelativePosition(n); }, u_);
301 }
302 
303 int IoStatementState::EndIoStatement() {
304   return std::visit([](auto &x) { return x.get().EndIoStatement(); }, u_);
305 }
306 
307 ConnectionState &IoStatementState::GetConnectionState() {
308   return std::visit(
309       [](auto &x) -> ConnectionState & { return x.get().GetConnectionState(); },
310       u_);
311 }
312 
313 MutableModes &IoStatementState::mutableModes() {
314   return std::visit(
315       [](auto &x) -> MutableModes & { return x.get().mutableModes(); }, u_);
316 }
317 
318 IoErrorHandler &IoStatementState::GetIoErrorHandler() const {
319   return std::visit(
320       [](auto &x) -> IoErrorHandler & {
321         return static_cast<IoErrorHandler &>(x.get());
322       },
323       u_);
324 }
325 
326 ExternalFileUnit *IoStatementState::GetExternalFileUnit() const {
327   return std::visit([](auto &x) { return x.get().GetExternalFileUnit(); }, u_);
328 }
329 
330 bool IoStatementState::EmitRepeated(char ch, std::size_t n) {
331   return std::visit(
332       [=](auto &x) {
333         for (std::size_t j{0}; j < n; ++j) {
334           if (!x.get().Emit(&ch, 1)) {
335             return false;
336           }
337         }
338         return true;
339       },
340       u_);
341 }
342 
343 bool IoStatementState::EmitField(
344     const char *p, std::size_t length, std::size_t width) {
345   if (width <= 0) {
346     width = static_cast<int>(length);
347   }
348   if (length > static_cast<std::size_t>(width)) {
349     return EmitRepeated('*', width);
350   } else {
351     return EmitRepeated(' ', static_cast<int>(width - length)) &&
352         Emit(p, length);
353   }
354 }
355 
356 std::optional<char32_t> IoStatementState::SkipSpaces(
357     std::optional<int> &remaining) {
358   while (!remaining || *remaining > 0) {
359     if (auto ch{GetCurrentChar()}) {
360       if (*ch != ' ' && *ch != '\t') {
361         return ch;
362       }
363       HandleRelativePosition(1);
364       if (remaining) {
365         --*remaining;
366       }
367     } else {
368       break;
369     }
370   }
371   return std::nullopt;
372 }
373 
374 std::optional<char32_t> IoStatementState::NextInField(
375     std::optional<int> &remaining) {
376   if (!remaining) { // list-directed or namelist: check for separators
377     if (auto next{GetCurrentChar()}) {
378       switch (*next) {
379       case ' ':
380       case '\t':
381       case ',':
382       case ';':
383       case '/':
384       case '(':
385       case ')':
386       case '\'':
387       case '"':
388       case '*':
389       case '\n': // for stream access
390         break;
391       default:
392         HandleRelativePosition(1);
393         return next;
394       }
395     }
396   } else if (*remaining > 0) {
397     if (auto next{GetCurrentChar()}) {
398       --*remaining;
399       HandleRelativePosition(1);
400       return next;
401     }
402     const ConnectionState &connection{GetConnectionState()};
403     if (!connection.IsAtEOF() && connection.isFixedRecordLength &&
404         connection.recordLength &&
405         connection.positionInRecord >= *connection.recordLength) {
406       if (connection.modes.pad) { // PAD='YES'
407         --*remaining;
408         return std::optional<char32_t>{' '};
409       }
410       IoErrorHandler &handler{GetIoErrorHandler()};
411       if (connection.nonAdvancing) {
412         handler.SignalEor();
413       } else {
414         handler.SignalError(IostatRecordReadOverrun);
415       }
416     }
417   }
418   return std::nullopt;
419 }
420 
421 std::optional<char32_t> IoStatementState::GetNextNonBlank() {
422   auto ch{GetCurrentChar()};
423   while (!ch || *ch == ' ' || *ch == '\t') {
424     if (ch) {
425       HandleRelativePosition(1);
426     } else if (!AdvanceRecord()) {
427       return std::nullopt;
428     }
429     ch = GetCurrentChar();
430   }
431   return ch;
432 }
433 
434 bool ListDirectedStatementState<Direction::Output>::NeedAdvance(
435     const ConnectionState &connection, std::size_t width) const {
436   return connection.positionInRecord > 0 &&
437       width > connection.RemainingSpaceInRecord();
438 }
439 
440 bool ListDirectedStatementState<Direction::Output>::EmitLeadingSpaceOrAdvance(
441     IoStatementState &io, std::size_t length, bool isCharacter) {
442   if (length == 0) {
443     return true;
444   }
445   const ConnectionState &connection{io.GetConnectionState()};
446   int space{connection.positionInRecord == 0 ||
447       !(isCharacter && lastWasUndelimitedCharacter)};
448   lastWasUndelimitedCharacter = false;
449   if (NeedAdvance(connection, space + length)) {
450     return io.AdvanceRecord();
451   }
452   if (space) {
453     return io.Emit(" ", 1);
454   }
455   return true;
456 }
457 
458 std::optional<DataEdit>
459 ListDirectedStatementState<Direction::Output>::GetNextDataEdit(
460     IoStatementState &io, int maxRepeat) {
461   DataEdit edit;
462   edit.descriptor = DataEdit::ListDirected;
463   edit.repeat = maxRepeat;
464   edit.modes = io.mutableModes();
465   return edit;
466 }
467 
468 std::optional<DataEdit>
469 ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
470     IoStatementState &io, int maxRepeat) {
471   // N.B. list-directed transfers cannot be nonadvancing (C1221)
472   ConnectionState &connection{io.GetConnectionState()};
473   DataEdit edit;
474   edit.descriptor = DataEdit::ListDirected;
475   edit.repeat = 1; // may be overridden below
476   edit.modes = connection.modes;
477   if (hitSlash_) { // everything after '/' is nullified
478     edit.descriptor = DataEdit::ListDirectedNullValue;
479     return edit;
480   }
481   char32_t comma{','};
482   if (io.mutableModes().editingFlags & decimalComma) {
483     comma = ';';
484   }
485   if (remaining_ > 0 && !realPart_) { // "r*c" repetition in progress
486     while (connection.currentRecordNumber > initialRecordNumber_) {
487       io.BackspaceRecord();
488     }
489     connection.HandleAbsolutePosition(initialPositionInRecord_);
490     if (!imaginaryPart_) {
491       edit.repeat = std::min<int>(remaining_, maxRepeat);
492       auto ch{io.GetNextNonBlank()};
493       if (!ch || *ch == ' ' || *ch == '\t' || *ch == comma) {
494         // "r*" repeated null
495         edit.descriptor = DataEdit::ListDirectedNullValue;
496       }
497     }
498     remaining_ -= edit.repeat;
499     return edit;
500   }
501   // Skip separators, handle a "r*c" repeat count; see 13.10.2 in Fortran 2018
502   auto ch{io.GetNextNonBlank()};
503   if (imaginaryPart_) {
504     imaginaryPart_ = false;
505     if (ch && *ch == ')') {
506       io.HandleRelativePosition(1);
507       ch = io.GetNextNonBlank();
508     }
509   } else if (realPart_) {
510     realPart_ = false;
511     imaginaryPart_ = true;
512     edit.descriptor = DataEdit::ListDirectedImaginaryPart;
513   }
514   if (!ch) {
515     return std::nullopt;
516   }
517   if (*ch == '/') {
518     hitSlash_ = true;
519     edit.descriptor = DataEdit::ListDirectedNullValue;
520     return edit;
521   }
522   bool isFirstItem{isFirstItem_};
523   isFirstItem_ = false;
524   if (*ch == comma) {
525     if (isFirstItem) {
526       edit.descriptor = DataEdit::ListDirectedNullValue;
527       return edit;
528     }
529     // Consume comma & whitespace after previous item.
530     io.HandleRelativePosition(1);
531     ch = io.GetNextNonBlank();
532     if (!ch) {
533       return std::nullopt;
534     }
535     if (*ch == comma || *ch == '/') {
536       edit.descriptor = DataEdit::ListDirectedNullValue;
537       return edit;
538     }
539   }
540   if (imaginaryPart_) { // can't repeat components
541     return edit;
542   }
543   if (*ch >= '0' && *ch <= '9') { // look for "r*" repetition count
544     auto start{connection.positionInRecord};
545     int r{0};
546     do {
547       static auto constexpr clamp{(std::numeric_limits<int>::max() - '9') / 10};
548       if (r >= clamp) {
549         r = 0;
550         break;
551       }
552       r = 10 * r + (*ch - '0');
553       io.HandleRelativePosition(1);
554       ch = io.GetCurrentChar();
555     } while (ch && *ch >= '0' && *ch <= '9');
556     if (r > 0 && ch && *ch == '*') { // subtle: r must be nonzero
557       io.HandleRelativePosition(1);
558       ch = io.GetCurrentChar();
559       if (ch && *ch == '/') { // r*/
560         hitSlash_ = true;
561         edit.descriptor = DataEdit::ListDirectedNullValue;
562         return edit;
563       }
564       if (!ch || *ch == ' ' || *ch == '\t' || *ch == comma) { // "r*" null
565         edit.descriptor = DataEdit::ListDirectedNullValue;
566       }
567       edit.repeat = std::min<int>(r, maxRepeat);
568       remaining_ = r - edit.repeat;
569       initialRecordNumber_ = connection.currentRecordNumber;
570       initialPositionInRecord_ = connection.positionInRecord;
571     } else { // not a repetition count, just an integer value; rewind
572       connection.positionInRecord = start;
573     }
574   }
575   if (!imaginaryPart_ && ch && *ch == '(') {
576     realPart_ = true;
577     io.HandleRelativePosition(1);
578     edit.descriptor = DataEdit::ListDirectedRealPart;
579   }
580   return edit;
581 }
582 
583 template <Direction DIR>
584 bool UnformattedIoStatementState<DIR>::Receive(
585     char *data, std::size_t bytes, std::size_t elementBytes) {
586   if constexpr (DIR == Direction::Output) {
587     this->Crash(
588         "UnformattedIoStatementState::Receive() called for output statement");
589   }
590   return this->unit().Receive(data, bytes, elementBytes, *this);
591 }
592 
593 template <Direction DIR>
594 bool UnformattedIoStatementState<DIR>::Emit(
595     const char *data, std::size_t bytes, std::size_t elementBytes) {
596   if constexpr (DIR == Direction::Input) {
597     this->Crash(
598         "UnformattedIoStatementState::Emit() called for input statement");
599   }
600   return ExternalIoStatementState<DIR>::Emit(data, bytes, elementBytes);
601 }
602 
603 template <Direction DIR>
604 int UnformattedIoStatementState<DIR>::EndIoStatement() {
605   ExternalFileUnit &unit{this->unit()};
606   if constexpr (DIR == Direction::Output) {
607     if (unit.access == Access::Sequential && !unit.isFixedRecordLength) {
608       // Append the length of a sequential unformatted variable-length record
609       // as its footer, then overwrite the reserved first four bytes of the
610       // record with its length as its header.  These four bytes were skipped
611       // over in BeginUnformattedOutput().
612       // TODO: Break very large records up into subrecords with negative
613       // headers &/or footers
614       union {
615         std::uint32_t u;
616         char c[sizeof u];
617       } u;
618       u.u = unit.furthestPositionInRecord - sizeof u;
619       // TODO: Convert record length to little-endian on big-endian host?
620       if (!(this->Emit(u.c, sizeof u) &&
621               (this->HandleAbsolutePosition(0), this->Emit(u.c, sizeof u)))) {
622         return false;
623       }
624     }
625   }
626   return ExternalIoStatementState<DIR>::EndIoStatement();
627 }
628 
629 template class InternalIoStatementState<Direction::Output>;
630 template class InternalIoStatementState<Direction::Input>;
631 template class InternalFormattedIoStatementState<Direction::Output>;
632 template class InternalFormattedIoStatementState<Direction::Input>;
633 template class InternalListIoStatementState<Direction::Output>;
634 template class InternalListIoStatementState<Direction::Input>;
635 template class ExternalIoStatementState<Direction::Output>;
636 template class ExternalIoStatementState<Direction::Input>;
637 template class ExternalFormattedIoStatementState<Direction::Output>;
638 template class ExternalFormattedIoStatementState<Direction::Input>;
639 template class ExternalListIoStatementState<Direction::Output>;
640 template class ExternalListIoStatementState<Direction::Input>;
641 template class UnformattedIoStatementState<Direction::Output>;
642 template class UnformattedIoStatementState<Direction::Input>;
643 
644 int ExternalMiscIoStatementState::EndIoStatement() {
645   ExternalFileUnit &ext{unit()};
646   switch (which_) {
647   case Flush:
648     ext.Flush(*this);
649     std::fflush(nullptr); // flushes C stdio output streams (12.9(2))
650     break;
651   case Backspace:
652     ext.BackspaceRecord(*this);
653     break;
654   case Endfile:
655     ext.Endfile(*this);
656     break;
657   case Rewind:
658     ext.Rewind(*this);
659     break;
660   }
661   return ExternalIoStatementBase::EndIoStatement();
662 }
663 
664 } // namespace Fortran::runtime::io
665