1 //===-- runtime/io-stmt.cpp -----------------------------------------------===//
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 "tools.h"
13 #include "unit.h"
14 #include "flang/Runtime/memory.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 bool IoStatementBase::Emit(const char *, std::size_t, std::size_t) {
25   return false;
26 }
27 
28 bool IoStatementBase::Emit(const char *, std::size_t) { return false; }
29 
30 bool IoStatementBase::Emit(const char16_t *, std::size_t) { return false; }
31 
32 bool IoStatementBase::Emit(const char32_t *, std::size_t) { return false; }
33 
34 std::size_t IoStatementBase::GetNextInputBytes(const char *&p) {
35   p = nullptr;
36   return 0;
37 }
38 
39 bool IoStatementBase::AdvanceRecord(int) { return false; }
40 
41 void IoStatementBase::BackspaceRecord() {}
42 
43 bool IoStatementBase::Receive(char *, std::size_t, std::size_t) {
44   return false;
45 }
46 
47 std::optional<DataEdit> IoStatementBase::GetNextDataEdit(
48     IoStatementState &, int) {
49   return std::nullopt;
50 }
51 
52 ExternalFileUnit *IoStatementBase::GetExternalFileUnit() const {
53   return nullptr;
54 }
55 
56 bool IoStatementBase::BeginReadingRecord() { return true; }
57 
58 void IoStatementBase::FinishReadingRecord() {}
59 
60 void IoStatementBase::HandleAbsolutePosition(std::int64_t) {}
61 
62 void IoStatementBase::HandleRelativePosition(std::int64_t) {}
63 
64 bool IoStatementBase::Inquire(InquiryKeywordHash, char *, std::size_t) {
65   return false;
66 }
67 
68 bool IoStatementBase::Inquire(InquiryKeywordHash, bool &) { return false; }
69 
70 bool IoStatementBase::Inquire(InquiryKeywordHash, std::int64_t, bool &) {
71   return false;
72 }
73 
74 bool IoStatementBase::Inquire(InquiryKeywordHash, std::int64_t &) {
75   return false;
76 }
77 
78 void IoStatementBase::BadInquiryKeywordHashCrash(InquiryKeywordHash inquiry) {
79   char buffer[16];
80   const char *decode{InquiryKeywordHashDecode(buffer, sizeof buffer, inquiry)};
81   Crash("bad InquiryKeywordHash 0x%x (%s)", inquiry,
82       decode ? decode : "(cannot decode)");
83 }
84 
85 template <Direction DIR, typename CHAR>
86 InternalIoStatementState<DIR, CHAR>::InternalIoStatementState(
87     Buffer scalar, std::size_t length, const char *sourceFile, int sourceLine)
88     : IoStatementBase{sourceFile, sourceLine}, unit_{scalar, length} {}
89 
90 template <Direction DIR, typename CHAR>
91 InternalIoStatementState<DIR, CHAR>::InternalIoStatementState(
92     const Descriptor &d, const char *sourceFile, int sourceLine)
93     : IoStatementBase{sourceFile, sourceLine}, unit_{d, *this} {}
94 
95 template <Direction DIR, typename CHAR>
96 bool InternalIoStatementState<DIR, CHAR>::Emit(
97     const CharType *data, std::size_t chars) {
98   if constexpr (DIR == Direction::Input) {
99     Crash("InternalIoStatementState<Direction::Input>::Emit() called");
100     return false;
101   }
102   return unit_.Emit(data, chars * sizeof(CharType), *this);
103 }
104 
105 template <Direction DIR, typename CHAR>
106 std::size_t InternalIoStatementState<DIR, CHAR>::GetNextInputBytes(
107     const char *&p) {
108   return unit_.GetNextInputBytes(p, *this);
109 }
110 
111 template <Direction DIR, typename CHAR>
112 bool InternalIoStatementState<DIR, CHAR>::AdvanceRecord(int n) {
113   while (n-- > 0) {
114     if (!unit_.AdvanceRecord(*this)) {
115       return false;
116     }
117   }
118   return true;
119 }
120 
121 template <Direction DIR, typename CHAR>
122 void InternalIoStatementState<DIR, CHAR>::BackspaceRecord() {
123   unit_.BackspaceRecord(*this);
124 }
125 
126 template <Direction DIR, typename CHAR>
127 int InternalIoStatementState<DIR, CHAR>::EndIoStatement() {
128   if constexpr (DIR == Direction::Output) {
129     unit_.EndIoStatement(); // fill
130   }
131   auto result{IoStatementBase::EndIoStatement()};
132   if (free_) {
133     FreeMemory(this);
134   }
135   return result;
136 }
137 
138 template <Direction DIR, typename CHAR>
139 void InternalIoStatementState<DIR, CHAR>::HandleAbsolutePosition(
140     std::int64_t n) {
141   return unit_.HandleAbsolutePosition(n);
142 }
143 
144 template <Direction DIR, typename CHAR>
145 void InternalIoStatementState<DIR, CHAR>::HandleRelativePosition(
146     std::int64_t n) {
147   return unit_.HandleRelativePosition(n);
148 }
149 
150 template <Direction DIR, typename CHAR>
151 InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState(
152     Buffer buffer, std::size_t length, const CHAR *format,
153     std::size_t formatLength, const char *sourceFile, int sourceLine)
154     : InternalIoStatementState<DIR, CHAR>{buffer, length, sourceFile,
155           sourceLine},
156       ioStatementState_{*this}, format_{*this, format, formatLength} {}
157 
158 template <Direction DIR, typename CHAR>
159 InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState(
160     const Descriptor &d, const CHAR *format, std::size_t formatLength,
161     const char *sourceFile, int sourceLine)
162     : InternalIoStatementState<DIR, CHAR>{d, sourceFile, sourceLine},
163       ioStatementState_{*this}, format_{*this, format, formatLength} {}
164 
165 template <Direction DIR, typename CHAR>
166 int InternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
167   if constexpr (DIR == Direction::Output) {
168     format_.Finish(*this); // ignore any remaining input positioning actions
169   }
170   return InternalIoStatementState<DIR, CHAR>::EndIoStatement();
171 }
172 
173 template <Direction DIR, typename CHAR>
174 InternalListIoStatementState<DIR, CHAR>::InternalListIoStatementState(
175     Buffer buffer, std::size_t length, const char *sourceFile, int sourceLine)
176     : InternalIoStatementState<DIR, CharType>{buffer, length, sourceFile,
177           sourceLine},
178       ioStatementState_{*this} {}
179 
180 template <Direction DIR, typename CHAR>
181 InternalListIoStatementState<DIR, CHAR>::InternalListIoStatementState(
182     const Descriptor &d, const char *sourceFile, int sourceLine)
183     : InternalIoStatementState<DIR, CharType>{d, sourceFile, sourceLine},
184       ioStatementState_{*this} {}
185 
186 ExternalIoStatementBase::ExternalIoStatementBase(
187     ExternalFileUnit &unit, const char *sourceFile, int sourceLine)
188     : IoStatementBase{sourceFile, sourceLine}, unit_{unit} {}
189 
190 MutableModes &ExternalIoStatementBase::mutableModes() { return unit_.modes; }
191 
192 ConnectionState &ExternalIoStatementBase::GetConnectionState() { return unit_; }
193 
194 int ExternalIoStatementBase::EndIoStatement() {
195   if (mutableModes().nonAdvancing) {
196     unit_.leftTabLimit = unit_.furthestPositionInRecord;
197   } else {
198     unit_.leftTabLimit.reset();
199   }
200   auto result{IoStatementBase::EndIoStatement()};
201   unit_.EndIoStatement(); // annihilates *this in unit_.u_
202   return result;
203 }
204 
205 void OpenStatementState::set_path(const char *path, std::size_t length) {
206   pathLength_ = TrimTrailingSpaces(path, length);
207   path_ = SaveDefaultCharacter(path, pathLength_, *this);
208 }
209 
210 int OpenStatementState::EndIoStatement() {
211   if (path_.get() || wasExtant_ ||
212       (status_ && *status_ == OpenStatus::Scratch)) {
213     unit().OpenUnit(status_, action_, position_, std::move(path_), pathLength_,
214         convert_, *this);
215   } else {
216     unit().OpenAnonymousUnit(status_, action_, position_, convert_, *this);
217   }
218   if (access_) {
219     if (*access_ != unit().access) {
220       if (wasExtant_) {
221         SignalError("ACCESS= may not be changed on an open unit");
222       }
223     }
224     unit().access = *access_;
225   }
226   if (!unit().isUnformatted) {
227     unit().isUnformatted = isUnformatted_;
228   }
229   if (isUnformatted_ && *isUnformatted_ != *unit().isUnformatted) {
230     if (wasExtant_) {
231       SignalError("FORM= may not be changed on an open unit");
232     }
233     unit().isUnformatted = *isUnformatted_;
234   }
235   if (!unit().isUnformatted) {
236     // Set default format (C.7.4 point 2).
237     unit().isUnformatted = unit().access != Access::Sequential;
238   }
239   return ExternalIoStatementBase::EndIoStatement();
240 }
241 
242 int CloseStatementState::EndIoStatement() {
243   int result{ExternalIoStatementBase::EndIoStatement()};
244   unit().CloseUnit(status_, *this);
245   unit().DestroyClosed();
246   return result;
247 }
248 
249 int NoUnitIoStatementState::EndIoStatement() {
250   auto result{IoStatementBase::EndIoStatement()};
251   FreeMemory(this);
252   return result;
253 }
254 
255 template <Direction DIR>
256 ExternalIoStatementState<DIR>::ExternalIoStatementState(
257     ExternalFileUnit &unit, const char *sourceFile, int sourceLine)
258     : ExternalIoStatementBase{unit, sourceFile, sourceLine}, mutableModes_{
259                                                                  unit.modes} {
260   if constexpr (DIR == Direction::Output) {
261     // If the last statement was a non advancing IO input statement, the unit
262     // furthestPositionInRecord was not advanced, but the positionInRecord may
263     // have been advanced. Advance furthestPositionInRecord here to avoid
264     // overwriting the part of the record that has been read with blanks.
265     unit.furthestPositionInRecord =
266         std::max(unit.furthestPositionInRecord, unit.positionInRecord);
267   }
268 }
269 
270 template <Direction DIR> int ExternalIoStatementState<DIR>::EndIoStatement() {
271   if constexpr (DIR == Direction::Input) {
272     BeginReadingRecord(); // in case there were no I/O items
273     if (!mutableModes().nonAdvancing || GetIoStat() == IostatEor) {
274       FinishReadingRecord();
275     }
276   } else {
277     if (!mutableModes().nonAdvancing) {
278       unit().AdvanceRecord(*this);
279     }
280     unit().FlushIfTerminal(*this);
281   }
282   return ExternalIoStatementBase::EndIoStatement();
283 }
284 
285 template <Direction DIR>
286 bool ExternalIoStatementState<DIR>::Emit(
287     const char *data, std::size_t bytes, std::size_t elementBytes) {
288   if constexpr (DIR == Direction::Input) {
289     Crash("ExternalIoStatementState::Emit(char) called for input statement");
290   }
291   return unit().Emit(data, bytes, elementBytes, *this);
292 }
293 
294 template <Direction DIR>
295 bool ExternalIoStatementState<DIR>::Emit(const char *data, std::size_t bytes) {
296   if constexpr (DIR == Direction::Input) {
297     Crash("ExternalIoStatementState::Emit(char) called for input statement");
298   }
299   return unit().Emit(data, bytes, 0, *this);
300 }
301 
302 template <Direction DIR>
303 bool ExternalIoStatementState<DIR>::Emit(
304     const char16_t *data, std::size_t chars) {
305   if constexpr (DIR == Direction::Input) {
306     Crash(
307         "ExternalIoStatementState::Emit(char16_t) called for input statement");
308   }
309   // TODO: UTF-8 encoding
310   return unit().Emit(reinterpret_cast<const char *>(data), chars * sizeof *data,
311       sizeof *data, *this);
312 }
313 
314 template <Direction DIR>
315 bool ExternalIoStatementState<DIR>::Emit(
316     const char32_t *data, std::size_t chars) {
317   if constexpr (DIR == Direction::Input) {
318     Crash(
319         "ExternalIoStatementState::Emit(char32_t) called for input statement");
320   }
321   // TODO: UTF-8 encoding
322   return unit().Emit(reinterpret_cast<const char *>(data), chars * sizeof *data,
323       sizeof *data, *this);
324 }
325 
326 template <Direction DIR>
327 std::size_t ExternalIoStatementState<DIR>::GetNextInputBytes(const char *&p) {
328   return unit().GetNextInputBytes(p, *this);
329 }
330 
331 template <Direction DIR>
332 bool ExternalIoStatementState<DIR>::AdvanceRecord(int n) {
333   while (n-- > 0) {
334     if (!unit().AdvanceRecord(*this)) {
335       return false;
336     }
337   }
338   return true;
339 }
340 
341 template <Direction DIR> void ExternalIoStatementState<DIR>::BackspaceRecord() {
342   unit().BackspaceRecord(*this);
343 }
344 
345 template <Direction DIR>
346 void ExternalIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) {
347   return unit().HandleAbsolutePosition(n);
348 }
349 
350 template <Direction DIR>
351 void ExternalIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) {
352   return unit().HandleRelativePosition(n);
353 }
354 
355 template <Direction DIR>
356 bool ExternalIoStatementState<DIR>::BeginReadingRecord() {
357   if constexpr (DIR == Direction::Input) {
358     return unit().BeginReadingRecord(*this);
359   } else {
360     Crash("ExternalIoStatementState<Direction::Output>::BeginReadingRecord() "
361           "called");
362     return false;
363   }
364 }
365 
366 template <Direction DIR>
367 void ExternalIoStatementState<DIR>::FinishReadingRecord() {
368   if constexpr (DIR == Direction::Input) {
369     unit().FinishReadingRecord(*this);
370   } else {
371     Crash("ExternalIoStatementState<Direction::Output>::FinishReadingRecord() "
372           "called");
373   }
374 }
375 
376 template <Direction DIR, typename CHAR>
377 ExternalFormattedIoStatementState<DIR, CHAR>::ExternalFormattedIoStatementState(
378     ExternalFileUnit &unit, const CHAR *format, std::size_t formatLength,
379     const char *sourceFile, int sourceLine)
380     : ExternalIoStatementState<DIR>{unit, sourceFile, sourceLine},
381       format_{*this, format, formatLength} {}
382 
383 template <Direction DIR, typename CHAR>
384 int ExternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
385   if constexpr (DIR == Direction::Input) {
386     this->BeginReadingRecord(); // in case there were no I/O items
387   }
388   format_.Finish(*this);
389   return ExternalIoStatementState<DIR>::EndIoStatement();
390 }
391 
392 std::optional<DataEdit> IoStatementState::GetNextDataEdit(int n) {
393   return std::visit(
394       [&](auto &x) { return x.get().GetNextDataEdit(*this, n); }, u_);
395 }
396 
397 bool IoStatementState::Emit(
398     const char *data, std::size_t n, std::size_t elementBytes) {
399   return std::visit(
400       [=](auto &x) { return x.get().Emit(data, n, elementBytes); }, u_);
401 }
402 
403 bool IoStatementState::Emit(const char *data, std::size_t n) {
404   return std::visit([=](auto &x) { return x.get().Emit(data, n); }, u_);
405 }
406 
407 bool IoStatementState::Emit(const char16_t *data, std::size_t chars) {
408   return std::visit([=](auto &x) { return x.get().Emit(data, chars); }, u_);
409 }
410 
411 bool IoStatementState::Emit(const char32_t *data, std::size_t chars) {
412   return std::visit([=](auto &x) { return x.get().Emit(data, chars); }, u_);
413 }
414 
415 bool IoStatementState::Receive(
416     char *data, std::size_t n, std::size_t elementBytes) {
417   return std::visit(
418       [=](auto &x) { return x.get().Receive(data, n, elementBytes); }, u_);
419 }
420 
421 std::size_t IoStatementState::GetNextInputBytes(const char *&p) {
422   return std::visit([&](auto &x) { return x.get().GetNextInputBytes(p); }, u_);
423 }
424 
425 bool IoStatementState::AdvanceRecord(int n) {
426   return std::visit([=](auto &x) { return x.get().AdvanceRecord(n); }, u_);
427 }
428 
429 void IoStatementState::BackspaceRecord() {
430   std::visit([](auto &x) { x.get().BackspaceRecord(); }, u_);
431 }
432 
433 void IoStatementState::HandleRelativePosition(std::int64_t n) {
434   std::visit([=](auto &x) { x.get().HandleRelativePosition(n); }, u_);
435 }
436 
437 void IoStatementState::HandleAbsolutePosition(std::int64_t n) {
438   std::visit([=](auto &x) { x.get().HandleAbsolutePosition(n); }, u_);
439 }
440 
441 int IoStatementState::EndIoStatement() {
442   return std::visit([](auto &x) { return x.get().EndIoStatement(); }, u_);
443 }
444 
445 ConnectionState &IoStatementState::GetConnectionState() {
446   return std::visit(
447       [](auto &x) -> ConnectionState & { return x.get().GetConnectionState(); },
448       u_);
449 }
450 
451 MutableModes &IoStatementState::mutableModes() {
452   return std::visit(
453       [](auto &x) -> MutableModes & { return x.get().mutableModes(); }, u_);
454 }
455 
456 bool IoStatementState::BeginReadingRecord() {
457   return std::visit([](auto &x) { return x.get().BeginReadingRecord(); }, u_);
458 }
459 
460 IoErrorHandler &IoStatementState::GetIoErrorHandler() const {
461   return std::visit(
462       [](auto &x) -> IoErrorHandler & {
463         return static_cast<IoErrorHandler &>(x.get());
464       },
465       u_);
466 }
467 
468 ExternalFileUnit *IoStatementState::GetExternalFileUnit() const {
469   return std::visit([](auto &x) { return x.get().GetExternalFileUnit(); }, u_);
470 }
471 
472 bool IoStatementState::EmitRepeated(char ch, std::size_t n) {
473   return std::visit(
474       [=](auto &x) {
475         for (std::size_t j{0}; j < n; ++j) {
476           if (!x.get().Emit(&ch, 1)) {
477             return false;
478           }
479         }
480         return true;
481       },
482       u_);
483 }
484 
485 bool IoStatementState::EmitField(
486     const char *p, std::size_t length, std::size_t width) {
487   if (width <= 0) {
488     width = static_cast<int>(length);
489   }
490   if (length > static_cast<std::size_t>(width)) {
491     return EmitRepeated('*', width);
492   } else {
493     return EmitRepeated(' ', static_cast<int>(width - length)) &&
494         Emit(p, length);
495   }
496 }
497 
498 bool IoStatementState::Inquire(
499     InquiryKeywordHash inquiry, char *out, std::size_t chars) {
500   return std::visit(
501       [&](auto &x) { return x.get().Inquire(inquiry, out, chars); }, u_);
502 }
503 
504 bool IoStatementState::Inquire(InquiryKeywordHash inquiry, bool &out) {
505   return std::visit([&](auto &x) { return x.get().Inquire(inquiry, out); }, u_);
506 }
507 
508 bool IoStatementState::Inquire(
509     InquiryKeywordHash inquiry, std::int64_t id, bool &out) {
510   return std::visit(
511       [&](auto &x) { return x.get().Inquire(inquiry, id, out); }, u_);
512 }
513 
514 bool IoStatementState::Inquire(InquiryKeywordHash inquiry, std::int64_t &n) {
515   return std::visit([&](auto &x) { return x.get().Inquire(inquiry, n); }, u_);
516 }
517 
518 void IoStatementState::GotChar(int n) {
519   if (auto *formattedIn{
520           get_if<FormattedIoStatementState<Direction::Input>>()}) {
521     formattedIn->GotChar(n);
522   } else {
523     GetIoErrorHandler().Crash("IoStatementState::GotChar() called for "
524                               "statement that is not formatted input");
525   }
526 }
527 
528 std::size_t
529 FormattedIoStatementState<Direction::Input>::GetEditDescriptorChars() const {
530   return chars_;
531 }
532 
533 void FormattedIoStatementState<Direction::Input>::GotChar(int n) {
534   chars_ += n;
535 }
536 
537 bool ListDirectedStatementState<Direction::Output>::EmitLeadingSpaceOrAdvance(
538     IoStatementState &io, std::size_t length, bool isCharacter) {
539   if (length == 0) {
540     return true;
541   }
542   const ConnectionState &connection{io.GetConnectionState()};
543   int space{connection.positionInRecord == 0 ||
544       !(isCharacter && lastWasUndelimitedCharacter())};
545   set_lastWasUndelimitedCharacter(false);
546   if (connection.NeedAdvance(space + length)) {
547     return io.AdvanceRecord();
548   }
549   if (space) {
550     return io.Emit(" ", 1);
551   }
552   return true;
553 }
554 
555 std::optional<DataEdit>
556 ListDirectedStatementState<Direction::Output>::GetNextDataEdit(
557     IoStatementState &io, int maxRepeat) {
558   DataEdit edit;
559   edit.descriptor = DataEdit::ListDirected;
560   edit.repeat = maxRepeat;
561   edit.modes = io.mutableModes();
562   return edit;
563 }
564 
565 std::optional<DataEdit>
566 ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
567     IoStatementState &io, int maxRepeat) {
568   // N.B. list-directed transfers cannot be nonadvancing (C1221)
569   ConnectionState &connection{io.GetConnectionState()};
570   DataEdit edit;
571   edit.descriptor = DataEdit::ListDirected;
572   edit.repeat = 1; // may be overridden below
573   edit.modes = connection.modes;
574   if (hitSlash_) { // everything after '/' is nullified
575     edit.descriptor = DataEdit::ListDirectedNullValue;
576     return edit;
577   }
578   char32_t comma{','};
579   if (io.mutableModes().editingFlags & decimalComma) {
580     comma = ';';
581   }
582   if (remaining_ > 0 && !realPart_) { // "r*c" repetition in progress
583     RUNTIME_CHECK(io.GetIoErrorHandler(), repeatPosition_.has_value());
584     repeatPosition_.reset(); // restores the saved position
585     if (!imaginaryPart_) {
586       edit.repeat = std::min<int>(remaining_, maxRepeat);
587       auto ch{io.GetCurrentChar()};
588       if (!ch || *ch == ' ' || *ch == '\t' || *ch == comma) {
589         // "r*" repeated null
590         edit.descriptor = DataEdit::ListDirectedNullValue;
591       }
592     }
593     remaining_ -= edit.repeat;
594     if (remaining_ > 0) {
595       repeatPosition_.emplace(io);
596     }
597     return edit;
598   }
599   // Skip separators, handle a "r*c" repeat count; see 13.10.2 in Fortran 2018
600   if (imaginaryPart_) {
601     imaginaryPart_ = false;
602   } else if (realPart_) {
603     realPart_ = false;
604     imaginaryPart_ = true;
605     edit.descriptor = DataEdit::ListDirectedImaginaryPart;
606   }
607   auto ch{io.GetNextNonBlank()};
608   if (ch && *ch == comma && eatComma_) {
609     // Consume comma & whitespace after previous item.
610     // This includes the comma between real and imaginary components
611     // in list-directed/NAMELIST complex input.
612     io.HandleRelativePosition(1);
613     ch = io.GetNextNonBlank();
614   }
615   eatComma_ = true;
616   if (!ch) {
617     return std::nullopt;
618   }
619   if (*ch == '/') {
620     hitSlash_ = true;
621     edit.descriptor = DataEdit::ListDirectedNullValue;
622     return edit;
623   }
624   if (*ch == comma) { // separator: null value
625     edit.descriptor = DataEdit::ListDirectedNullValue;
626     return edit;
627   }
628   if (imaginaryPart_) { // can't repeat components
629     return edit;
630   }
631   if (*ch >= '0' && *ch <= '9') { // look for "r*" repetition count
632     auto start{connection.positionInRecord};
633     int r{0};
634     do {
635       static auto constexpr clamp{(std::numeric_limits<int>::max() - '9') / 10};
636       if (r >= clamp) {
637         r = 0;
638         break;
639       }
640       r = 10 * r + (*ch - '0');
641       io.HandleRelativePosition(1);
642       ch = io.GetCurrentChar();
643     } while (ch && *ch >= '0' && *ch <= '9');
644     if (r > 0 && ch && *ch == '*') { // subtle: r must be nonzero
645       io.HandleRelativePosition(1);
646       ch = io.GetCurrentChar();
647       if (ch && *ch == '/') { // r*/
648         hitSlash_ = true;
649         edit.descriptor = DataEdit::ListDirectedNullValue;
650         return edit;
651       }
652       if (!ch || *ch == ' ' || *ch == '\t' || *ch == comma) { // "r*" null
653         edit.descriptor = DataEdit::ListDirectedNullValue;
654       }
655       edit.repeat = std::min<int>(r, maxRepeat);
656       remaining_ = r - edit.repeat;
657       if (remaining_ > 0) {
658         repeatPosition_.emplace(io);
659       }
660     } else { // not a repetition count, just an integer value; rewind
661       connection.positionInRecord = start;
662     }
663   }
664   if (!imaginaryPart_ && ch && *ch == '(') {
665     realPart_ = true;
666     io.HandleRelativePosition(1);
667     edit.descriptor = DataEdit::ListDirectedRealPart;
668   }
669   return edit;
670 }
671 
672 template <Direction DIR>
673 bool ExternalUnformattedIoStatementState<DIR>::Receive(
674     char *data, std::size_t bytes, std::size_t elementBytes) {
675   if constexpr (DIR == Direction::Output) {
676     this->Crash("ExternalUnformattedIoStatementState::Receive() called for "
677                 "output statement");
678   }
679   return this->unit().Receive(data, bytes, elementBytes, *this);
680 }
681 
682 template <Direction DIR>
683 ChildIoStatementState<DIR>::ChildIoStatementState(
684     ChildIo &child, const char *sourceFile, int sourceLine)
685     : IoStatementBase{sourceFile, sourceLine}, child_{child} {}
686 
687 template <Direction DIR>
688 MutableModes &ChildIoStatementState<DIR>::mutableModes() {
689   return child_.parent().mutableModes();
690 }
691 
692 template <Direction DIR>
693 ConnectionState &ChildIoStatementState<DIR>::GetConnectionState() {
694   return child_.parent().GetConnectionState();
695 }
696 
697 template <Direction DIR>
698 ExternalFileUnit *ChildIoStatementState<DIR>::GetExternalFileUnit() const {
699   return child_.parent().GetExternalFileUnit();
700 }
701 
702 template <Direction DIR> int ChildIoStatementState<DIR>::EndIoStatement() {
703   auto result{IoStatementBase::EndIoStatement()};
704   child_.EndIoStatement(); // annihilates *this in child_.u_
705   return result;
706 }
707 
708 template <Direction DIR>
709 bool ChildIoStatementState<DIR>::Emit(
710     const char *data, std::size_t bytes, std::size_t elementBytes) {
711   return child_.parent().Emit(data, bytes, elementBytes);
712 }
713 
714 template <Direction DIR>
715 bool ChildIoStatementState<DIR>::Emit(const char *data, std::size_t bytes) {
716   return child_.parent().Emit(data, bytes);
717 }
718 
719 template <Direction DIR>
720 bool ChildIoStatementState<DIR>::Emit(const char16_t *data, std::size_t chars) {
721   return child_.parent().Emit(data, chars);
722 }
723 
724 template <Direction DIR>
725 bool ChildIoStatementState<DIR>::Emit(const char32_t *data, std::size_t chars) {
726   return child_.parent().Emit(data, chars);
727 }
728 
729 template <Direction DIR>
730 std::size_t ChildIoStatementState<DIR>::GetNextInputBytes(const char *&p) {
731   return child_.parent().GetNextInputBytes(p);
732 }
733 
734 template <Direction DIR>
735 void ChildIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) {
736   return child_.parent().HandleAbsolutePosition(n);
737 }
738 
739 template <Direction DIR>
740 void ChildIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) {
741   return child_.parent().HandleRelativePosition(n);
742 }
743 
744 template <Direction DIR, typename CHAR>
745 ChildFormattedIoStatementState<DIR, CHAR>::ChildFormattedIoStatementState(
746     ChildIo &child, const CHAR *format, std::size_t formatLength,
747     const char *sourceFile, int sourceLine)
748     : ChildIoStatementState<DIR>{child, sourceFile, sourceLine},
749       mutableModes_{child.parent().mutableModes()}, format_{*this, format,
750                                                         formatLength} {}
751 
752 template <Direction DIR, typename CHAR>
753 int ChildFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
754   format_.Finish(*this);
755   return ChildIoStatementState<DIR>::EndIoStatement();
756 }
757 
758 template <Direction DIR, typename CHAR>
759 bool ChildFormattedIoStatementState<DIR, CHAR>::AdvanceRecord(int) {
760   return false; // no can do in a child I/O
761 }
762 
763 template <Direction DIR>
764 bool ChildUnformattedIoStatementState<DIR>::Receive(
765     char *data, std::size_t bytes, std::size_t elementBytes) {
766   return this->child().parent().Receive(data, bytes, elementBytes);
767 }
768 
769 template class InternalIoStatementState<Direction::Output>;
770 template class InternalIoStatementState<Direction::Input>;
771 template class InternalFormattedIoStatementState<Direction::Output>;
772 template class InternalFormattedIoStatementState<Direction::Input>;
773 template class InternalListIoStatementState<Direction::Output>;
774 template class InternalListIoStatementState<Direction::Input>;
775 template class ExternalIoStatementState<Direction::Output>;
776 template class ExternalIoStatementState<Direction::Input>;
777 template class ExternalFormattedIoStatementState<Direction::Output>;
778 template class ExternalFormattedIoStatementState<Direction::Input>;
779 template class ExternalListIoStatementState<Direction::Output>;
780 template class ExternalListIoStatementState<Direction::Input>;
781 template class ExternalUnformattedIoStatementState<Direction::Output>;
782 template class ExternalUnformattedIoStatementState<Direction::Input>;
783 template class ChildIoStatementState<Direction::Output>;
784 template class ChildIoStatementState<Direction::Input>;
785 template class ChildFormattedIoStatementState<Direction::Output>;
786 template class ChildFormattedIoStatementState<Direction::Input>;
787 template class ChildListIoStatementState<Direction::Output>;
788 template class ChildListIoStatementState<Direction::Input>;
789 template class ChildUnformattedIoStatementState<Direction::Output>;
790 template class ChildUnformattedIoStatementState<Direction::Input>;
791 
792 int ExternalMiscIoStatementState::EndIoStatement() {
793   ExternalFileUnit &ext{unit()};
794   switch (which_) {
795   case Flush:
796     ext.FlushOutput(*this);
797     std::fflush(nullptr); // flushes C stdio output streams (12.9(2))
798     break;
799   case Backspace:
800     ext.BackspaceRecord(*this);
801     break;
802   case Endfile:
803     ext.Endfile(*this);
804     break;
805   case Rewind:
806     ext.Rewind(*this);
807     break;
808   }
809   return ExternalIoStatementBase::EndIoStatement();
810 }
811 
812 InquireUnitState::InquireUnitState(
813     ExternalFileUnit &unit, const char *sourceFile, int sourceLine)
814     : ExternalIoStatementBase{unit, sourceFile, sourceLine} {}
815 
816 bool InquireUnitState::Inquire(
817     InquiryKeywordHash inquiry, char *result, std::size_t length) {
818   if (unit().createdForInternalChildIo()) {
819     SignalError(IostatInquireInternalUnit,
820         "INQUIRE of unit created for defined derived type I/O of an internal "
821         "unit");
822     return false;
823   }
824   const char *str{nullptr};
825   switch (inquiry) {
826   case HashInquiryKeyword("ACCESS"):
827     if (!unit().IsConnected()) {
828       str = "UNDEFINED";
829     } else {
830       switch (unit().access) {
831       case Access::Sequential:
832         str = "SEQUENTIAL";
833         break;
834       case Access::Direct:
835         str = "DIRECT";
836         break;
837       case Access::Stream:
838         str = "STREAM";
839         break;
840       }
841     }
842     break;
843   case HashInquiryKeyword("ACTION"):
844     str = !unit().IsConnected() ? "UNDEFINED"
845         : unit().mayWrite()     ? unit().mayRead() ? "READWRITE" : "WRITE"
846                                 : "READ";
847     break;
848   case HashInquiryKeyword("ASYNCHRONOUS"):
849     str = !unit().IsConnected()    ? "UNDEFINED"
850         : unit().mayAsynchronous() ? "YES"
851                                    : "NO";
852     break;
853   case HashInquiryKeyword("BLANK"):
854     str = !unit().IsConnected() || unit().isUnformatted.value_or(true)
855         ? "UNDEFINED"
856         : unit().modes.editingFlags & blankZero ? "ZERO"
857                                                 : "NULL";
858     break;
859   case HashInquiryKeyword("CARRIAGECONTROL"):
860     str = "LIST";
861     break;
862   case HashInquiryKeyword("CONVERT"):
863     str = unit().swapEndianness() ? "SWAP" : "NATIVE";
864     break;
865   case HashInquiryKeyword("DECIMAL"):
866     str = !unit().IsConnected() || unit().isUnformatted.value_or(true)
867         ? "UNDEFINED"
868         : unit().modes.editingFlags & decimalComma ? "COMMA"
869                                                    : "POINT";
870     break;
871   case HashInquiryKeyword("DELIM"):
872     if (!unit().IsConnected() || unit().isUnformatted.value_or(true)) {
873       str = "UNDEFINED";
874     } else {
875       switch (unit().modes.delim) {
876       case '\'':
877         str = "APOSTROPHE";
878         break;
879       case '"':
880         str = "QUOTE";
881         break;
882       default:
883         str = "NONE";
884         break;
885       }
886     }
887     break;
888   case HashInquiryKeyword("DIRECT"):
889     str = !unit().IsConnected() ? "UNKNOWN"
890         : unit().access == Access::Direct ||
891             (unit().mayPosition() && unit().openRecl)
892         ? "YES"
893         : "NO";
894     break;
895   case HashInquiryKeyword("ENCODING"):
896     str = !unit().IsConnected()               ? "UNKNOWN"
897         : unit().isUnformatted.value_or(true) ? "UNDEFINED"
898         : unit().isUTF8                       ? "UTF-8"
899                                               : "ASCII";
900     break;
901   case HashInquiryKeyword("FORM"):
902     str = !unit().IsConnected() || !unit().isUnformatted ? "UNDEFINED"
903         : *unit().isUnformatted                          ? "UNFORMATTED"
904                                                          : "FORMATTED";
905     break;
906   case HashInquiryKeyword("FORMATTED"):
907     str = !unit().IsConnected() ? "UNDEFINED"
908         : !unit().isUnformatted ? "UNKNOWN"
909         : *unit().isUnformatted ? "NO"
910                                 : "YES";
911     break;
912   case HashInquiryKeyword("NAME"):
913     str = unit().path();
914     if (!str) {
915       return true; // result is undefined
916     }
917     break;
918   case HashInquiryKeyword("PAD"):
919     str = !unit().IsConnected() || unit().isUnformatted.value_or(true)
920         ? "UNDEFINED"
921         : unit().modes.pad ? "YES"
922                            : "NO";
923     break;
924   case HashInquiryKeyword("POSITION"):
925     if (!unit().IsConnected() || unit().access == Access::Direct) {
926       str = "UNDEFINED";
927     } else {
928       switch (unit().InquirePosition()) {
929       case Position::Rewind:
930         str = "REWIND";
931         break;
932       case Position::Append:
933         str = "APPEND";
934         break;
935       case Position::AsIs:
936         str = "ASIS";
937         break;
938       }
939     }
940     break;
941   case HashInquiryKeyword("READ"):
942     str = !unit().IsConnected() ? "UNDEFINED" : unit().mayRead() ? "YES" : "NO";
943     break;
944   case HashInquiryKeyword("READWRITE"):
945     str = !unit().IsConnected()                 ? "UNDEFINED"
946         : unit().mayRead() && unit().mayWrite() ? "YES"
947                                                 : "NO";
948     break;
949   case HashInquiryKeyword("ROUND"):
950     if (!unit().IsConnected() || unit().isUnformatted.value_or(true)) {
951       str = "UNDEFINED";
952     } else {
953       switch (unit().modes.round) {
954       case decimal::FortranRounding::RoundNearest:
955         str = "NEAREST";
956         break;
957       case decimal::FortranRounding::RoundUp:
958         str = "UP";
959         break;
960       case decimal::FortranRounding::RoundDown:
961         str = "DOWN";
962         break;
963       case decimal::FortranRounding::RoundToZero:
964         str = "ZERO";
965         break;
966       case decimal::FortranRounding::RoundCompatible:
967         str = "COMPATIBLE";
968         break;
969       }
970     }
971     break;
972   case HashInquiryKeyword("SEQUENTIAL"):
973     // "NO" for Direct, since Sequential would not work if
974     // the unit were reopened without RECL=.
975     str = !unit().IsConnected()               ? "UNKNOWN"
976         : unit().access == Access::Sequential ? "YES"
977                                               : "NO";
978     break;
979   case HashInquiryKeyword("SIGN"):
980     str = !unit().IsConnected() || unit().isUnformatted.value_or(true)
981         ? "UNDEFINED"
982         : unit().modes.editingFlags & signPlus ? "PLUS"
983                                                : "SUPPRESS";
984     break;
985   case HashInquiryKeyword("STREAM"):
986     str = !unit().IsConnected()           ? "UNKNOWN"
987         : unit().access == Access::Stream ? "YES"
988                                           : "NO";
989     break;
990   case HashInquiryKeyword("UNFORMATTED"):
991     str = !unit().IsConnected() || !unit().isUnformatted ? "UNKNOWN"
992         : *unit().isUnformatted                          ? "YES"
993                                                          : "NO";
994     break;
995   case HashInquiryKeyword("WRITE"):
996     str = !unit().IsConnected() ? "UNKNOWN" : unit().mayWrite() ? "YES" : "NO";
997     break;
998   }
999   if (str) {
1000     ToFortranDefaultCharacter(result, length, str);
1001     return true;
1002   } else {
1003     BadInquiryKeywordHashCrash(inquiry);
1004     return false;
1005   }
1006 }
1007 
1008 bool InquireUnitState::Inquire(InquiryKeywordHash inquiry, bool &result) {
1009   switch (inquiry) {
1010   case HashInquiryKeyword("EXIST"):
1011     result = true;
1012     return true;
1013   case HashInquiryKeyword("NAMED"):
1014     result = unit().path() != nullptr;
1015     return true;
1016   case HashInquiryKeyword("OPENED"):
1017     result = unit().IsConnected();
1018     return true;
1019   case HashInquiryKeyword("PENDING"):
1020     result = false; // asynchronous I/O is not implemented
1021     return true;
1022   default:
1023     BadInquiryKeywordHashCrash(inquiry);
1024     return false;
1025   }
1026 }
1027 
1028 bool InquireUnitState::Inquire(
1029     InquiryKeywordHash inquiry, std::int64_t, bool &result) {
1030   switch (inquiry) {
1031   case HashInquiryKeyword("PENDING"):
1032     result = false; // asynchronous I/O is not implemented
1033     return true;
1034   default:
1035     BadInquiryKeywordHashCrash(inquiry);
1036     return false;
1037   }
1038 }
1039 
1040 bool InquireUnitState::Inquire(
1041     InquiryKeywordHash inquiry, std::int64_t &result) {
1042   switch (inquiry) {
1043   case HashInquiryKeyword("NEXTREC"):
1044     if (unit().access == Access::Direct) {
1045       result = unit().currentRecordNumber;
1046     }
1047     return true;
1048   case HashInquiryKeyword("NUMBER"):
1049     result = unit().IsConnected() ? unit().unitNumber() : -1;
1050     return true;
1051   case HashInquiryKeyword("POS"):
1052     result = unit().position();
1053     return true;
1054   case HashInquiryKeyword("RECL"):
1055     if (!unit().IsConnected()) {
1056       result = -1;
1057     } else if (unit().access == Access::Stream) {
1058       result = -2;
1059     } else if (unit().openRecl) {
1060       result = *unit().openRecl;
1061     } else {
1062       result = std::numeric_limits<std::uint32_t>::max();
1063     }
1064     return true;
1065   case HashInquiryKeyword("SIZE"):
1066     result = -1;
1067     if (unit().IsConnected()) {
1068       if (auto size{unit().knownSize()}) {
1069         result = *size;
1070       }
1071     }
1072     return true;
1073   default:
1074     BadInquiryKeywordHashCrash(inquiry);
1075     return false;
1076   }
1077 }
1078 
1079 InquireNoUnitState::InquireNoUnitState(const char *sourceFile, int sourceLine)
1080     : NoUnitIoStatementState{sourceFile, sourceLine, *this} {}
1081 
1082 bool InquireNoUnitState::Inquire(
1083     InquiryKeywordHash inquiry, char *result, std::size_t length) {
1084   switch (inquiry) {
1085   case HashInquiryKeyword("ACCESS"):
1086   case HashInquiryKeyword("ACTION"):
1087   case HashInquiryKeyword("ASYNCHRONOUS"):
1088   case HashInquiryKeyword("BLANK"):
1089   case HashInquiryKeyword("CARRIAGECONTROL"):
1090   case HashInquiryKeyword("CONVERT"):
1091   case HashInquiryKeyword("DECIMAL"):
1092   case HashInquiryKeyword("DELIM"):
1093   case HashInquiryKeyword("FORM"):
1094   case HashInquiryKeyword("NAME"):
1095   case HashInquiryKeyword("PAD"):
1096   case HashInquiryKeyword("POSITION"):
1097   case HashInquiryKeyword("ROUND"):
1098   case HashInquiryKeyword("SIGN"):
1099     ToFortranDefaultCharacter(result, length, "UNDEFINED");
1100     return true;
1101   case HashInquiryKeyword("DIRECT"):
1102   case HashInquiryKeyword("ENCODING"):
1103   case HashInquiryKeyword("FORMATTED"):
1104   case HashInquiryKeyword("READ"):
1105   case HashInquiryKeyword("READWRITE"):
1106   case HashInquiryKeyword("SEQUENTIAL"):
1107   case HashInquiryKeyword("STREAM"):
1108   case HashInquiryKeyword("WRITE"):
1109   case HashInquiryKeyword("UNFORMATTED"):
1110     ToFortranDefaultCharacter(result, length, "UNKNONN");
1111     return true;
1112   default:
1113     BadInquiryKeywordHashCrash(inquiry);
1114     return false;
1115   }
1116 }
1117 
1118 bool InquireNoUnitState::Inquire(InquiryKeywordHash inquiry, bool &result) {
1119   switch (inquiry) {
1120   case HashInquiryKeyword("EXIST"):
1121     result = true;
1122     return true;
1123   case HashInquiryKeyword("NAMED"):
1124   case HashInquiryKeyword("OPENED"):
1125   case HashInquiryKeyword("PENDING"):
1126     result = false;
1127     return true;
1128   default:
1129     BadInquiryKeywordHashCrash(inquiry);
1130     return false;
1131   }
1132 }
1133 
1134 bool InquireNoUnitState::Inquire(
1135     InquiryKeywordHash inquiry, std::int64_t, bool &result) {
1136   switch (inquiry) {
1137   case HashInquiryKeyword("PENDING"):
1138     result = false;
1139     return true;
1140   default:
1141     BadInquiryKeywordHashCrash(inquiry);
1142     return false;
1143   }
1144 }
1145 
1146 bool InquireNoUnitState::Inquire(
1147     InquiryKeywordHash inquiry, std::int64_t &result) {
1148   switch (inquiry) {
1149   case HashInquiryKeyword("NEXTREC"):
1150   case HashInquiryKeyword("NUMBER"):
1151   case HashInquiryKeyword("POS"):
1152   case HashInquiryKeyword("RECL"):
1153   case HashInquiryKeyword("SIZE"):
1154     result = -1;
1155     return true;
1156   default:
1157     BadInquiryKeywordHashCrash(inquiry);
1158     return false;
1159   }
1160 }
1161 
1162 InquireUnconnectedFileState::InquireUnconnectedFileState(
1163     OwningPtr<char> &&path, const char *sourceFile, int sourceLine)
1164     : NoUnitIoStatementState{sourceFile, sourceLine, *this}, path_{std::move(
1165                                                                  path)} {}
1166 
1167 bool InquireUnconnectedFileState::Inquire(
1168     InquiryKeywordHash inquiry, char *result, std::size_t length) {
1169   const char *str{nullptr};
1170   switch (inquiry) {
1171   case HashInquiryKeyword("ACCESS"):
1172   case HashInquiryKeyword("ACTION"):
1173   case HashInquiryKeyword("ASYNCHRONOUS"):
1174   case HashInquiryKeyword("BLANK"):
1175   case HashInquiryKeyword("CARRIAGECONTROL"):
1176   case HashInquiryKeyword("CONVERT"):
1177   case HashInquiryKeyword("DECIMAL"):
1178   case HashInquiryKeyword("DELIM"):
1179   case HashInquiryKeyword("FORM"):
1180   case HashInquiryKeyword("PAD"):
1181   case HashInquiryKeyword("POSITION"):
1182   case HashInquiryKeyword("ROUND"):
1183   case HashInquiryKeyword("SIGN"):
1184     str = "UNDEFINED";
1185     break;
1186   case HashInquiryKeyword("DIRECT"):
1187   case HashInquiryKeyword("ENCODING"):
1188   case HashInquiryKeyword("FORMATTED"):
1189   case HashInquiryKeyword("SEQUENTIAL"):
1190   case HashInquiryKeyword("STREAM"):
1191   case HashInquiryKeyword("UNFORMATTED"):
1192     str = "UNKNONN";
1193     break;
1194   case HashInquiryKeyword("READ"):
1195     str = MayRead(path_.get()) ? "YES" : "NO";
1196     break;
1197   case HashInquiryKeyword("READWRITE"):
1198     str = MayReadAndWrite(path_.get()) ? "YES" : "NO";
1199     break;
1200   case HashInquiryKeyword("WRITE"):
1201     str = MayWrite(path_.get()) ? "YES" : "NO";
1202     break;
1203   case HashInquiryKeyword("NAME"):
1204     str = path_.get();
1205     if (!str) {
1206       return true; // result is undefined
1207     }
1208     break;
1209   }
1210   if (str) {
1211     ToFortranDefaultCharacter(result, length, str);
1212     return true;
1213   } else {
1214     BadInquiryKeywordHashCrash(inquiry);
1215     return false;
1216   }
1217 }
1218 
1219 bool InquireUnconnectedFileState::Inquire(
1220     InquiryKeywordHash inquiry, bool &result) {
1221   switch (inquiry) {
1222   case HashInquiryKeyword("EXIST"):
1223     result = IsExtant(path_.get());
1224     return true;
1225   case HashInquiryKeyword("NAMED"):
1226     result = true;
1227     return true;
1228   case HashInquiryKeyword("OPENED"):
1229     result = false;
1230     return true;
1231   case HashInquiryKeyword("PENDING"):
1232     result = false;
1233     return true;
1234   default:
1235     BadInquiryKeywordHashCrash(inquiry);
1236     return false;
1237   }
1238 }
1239 
1240 bool InquireUnconnectedFileState::Inquire(
1241     InquiryKeywordHash inquiry, std::int64_t, bool &result) {
1242   switch (inquiry) {
1243   case HashInquiryKeyword("PENDING"):
1244     result = false;
1245     return true;
1246   default:
1247     BadInquiryKeywordHashCrash(inquiry);
1248     return false;
1249   }
1250 }
1251 
1252 bool InquireUnconnectedFileState::Inquire(
1253     InquiryKeywordHash inquiry, std::int64_t &result) {
1254   switch (inquiry) {
1255   case HashInquiryKeyword("NEXTREC"):
1256   case HashInquiryKeyword("NUMBER"):
1257   case HashInquiryKeyword("POS"):
1258   case HashInquiryKeyword("RECL"):
1259   case HashInquiryKeyword("SIZE"):
1260     result = -1;
1261     return true;
1262   default:
1263     BadInquiryKeywordHashCrash(inquiry);
1264     return false;
1265   }
1266 }
1267 
1268 InquireIOLengthState::InquireIOLengthState(
1269     const char *sourceFile, int sourceLine)
1270     : NoUnitIoStatementState{sourceFile, sourceLine, *this} {}
1271 
1272 bool InquireIOLengthState::Emit(const char *, std::size_t n, std::size_t) {
1273   bytes_ += n;
1274   return true;
1275 }
1276 
1277 bool InquireIOLengthState::Emit(const char *p, std::size_t n) {
1278   bytes_ += sizeof *p * n;
1279   return true;
1280 }
1281 
1282 bool InquireIOLengthState::Emit(const char16_t *p, std::size_t n) {
1283   bytes_ += sizeof *p * n;
1284   return true;
1285 }
1286 
1287 bool InquireIOLengthState::Emit(const char32_t *p, std::size_t n) {
1288   bytes_ += sizeof *p * n;
1289   return true;
1290 }
1291 
1292 } // namespace Fortran::runtime::io
1293