xref: /llvm-project/flang/runtime/io-api.cpp (revision 27d666b9adc12a13f67ccde96d5b4d181f56ffc5)
1 //===-- runtime/io-api.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 // Implements the I/O statement API
10 
11 #include "flang/Runtime/io-api.h"
12 #include "descriptor-io.h"
13 #include "edit-input.h"
14 #include "edit-output.h"
15 #include "environment.h"
16 #include "format.h"
17 #include "io-stmt.h"
18 #include "terminator.h"
19 #include "tools.h"
20 #include "unit.h"
21 #include "flang/Runtime/descriptor.h"
22 #include "flang/Runtime/memory.h"
23 #include <cstdlib>
24 #include <memory>
25 
26 namespace Fortran::runtime::io {
27 
28 const char *InquiryKeywordHashDecode(
29     char *buffer, std::size_t n, InquiryKeywordHash hash) {
30   if (n < 1) {
31     return nullptr;
32   }
33   char *p{buffer + n};
34   *--p = '\0';
35   while (hash > 1) {
36     if (p < buffer) {
37       return nullptr;
38     }
39     *--p = 'A' + (hash % 26);
40     hash /= 26;
41   }
42   return hash == 1 ? p : nullptr;
43 }
44 
45 template <Direction DIR>
46 Cookie BeginInternalArrayListIO(const Descriptor &descriptor,
47     void ** /*scratchArea*/, std::size_t /*scratchBytes*/,
48     const char *sourceFile, int sourceLine) {
49   Terminator oom{sourceFile, sourceLine};
50   return &New<InternalListIoStatementState<DIR>>{oom}(
51       descriptor, sourceFile, sourceLine)
52               .release()
53               ->ioStatementState();
54 }
55 
56 Cookie IONAME(BeginInternalArrayListOutput)(const Descriptor &descriptor,
57     void **scratchArea, std::size_t scratchBytes, const char *sourceFile,
58     int sourceLine) {
59   return BeginInternalArrayListIO<Direction::Output>(
60       descriptor, scratchArea, scratchBytes, sourceFile, sourceLine);
61 }
62 
63 Cookie IONAME(BeginInternalArrayListInput)(const Descriptor &descriptor,
64     void **scratchArea, std::size_t scratchBytes, const char *sourceFile,
65     int sourceLine) {
66   return BeginInternalArrayListIO<Direction::Input>(
67       descriptor, scratchArea, scratchBytes, sourceFile, sourceLine);
68 }
69 
70 template <Direction DIR>
71 Cookie BeginInternalArrayFormattedIO(const Descriptor &descriptor,
72     const char *format, std::size_t formatLength,
73     const Descriptor *formatDescriptor, void ** /*scratchArea*/,
74     std::size_t /*scratchBytes*/, const char *sourceFile, int sourceLine) {
75   Terminator oom{sourceFile, sourceLine};
76   return &New<InternalFormattedIoStatementState<DIR>>{oom}(descriptor, format,
77       formatLength, formatDescriptor, sourceFile, sourceLine)
78               .release()
79               ->ioStatementState();
80 }
81 
82 Cookie IONAME(BeginInternalArrayFormattedOutput)(const Descriptor &descriptor,
83     const char *format, std::size_t formatLength,
84     const Descriptor *formatDescriptor, void **scratchArea,
85     std::size_t scratchBytes, const char *sourceFile, int sourceLine) {
86   return BeginInternalArrayFormattedIO<Direction::Output>(descriptor, format,
87       formatLength, formatDescriptor, scratchArea, scratchBytes, sourceFile,
88       sourceLine);
89 }
90 
91 Cookie IONAME(BeginInternalArrayFormattedInput)(const Descriptor &descriptor,
92     const char *format, std::size_t formatLength,
93     const Descriptor *formatDescriptor, void **scratchArea,
94     std::size_t scratchBytes, const char *sourceFile, int sourceLine) {
95   return BeginInternalArrayFormattedIO<Direction::Input>(descriptor, format,
96       formatLength, formatDescriptor, scratchArea, scratchBytes, sourceFile,
97       sourceLine);
98 }
99 
100 template <Direction DIR>
101 Cookie BeginInternalListIO(
102     std::conditional_t<DIR == Direction::Input, const char, char> *internal,
103     std::size_t internalLength, void ** /*scratchArea*/,
104     std::size_t /*scratchBytes*/, const char *sourceFile, int sourceLine) {
105   Terminator oom{sourceFile, sourceLine};
106   return &New<InternalListIoStatementState<DIR>>{oom}(
107       internal, internalLength, sourceFile, sourceLine)
108               .release()
109               ->ioStatementState();
110 }
111 
112 Cookie IONAME(BeginInternalListOutput)(char *internal,
113     std::size_t internalLength, void **scratchArea, std::size_t scratchBytes,
114     const char *sourceFile, int sourceLine) {
115   return BeginInternalListIO<Direction::Output>(internal, internalLength,
116       scratchArea, scratchBytes, sourceFile, sourceLine);
117 }
118 
119 Cookie IONAME(BeginInternalListInput)(const char *internal,
120     std::size_t internalLength, void **scratchArea, std::size_t scratchBytes,
121     const char *sourceFile, int sourceLine) {
122   return BeginInternalListIO<Direction::Input>(internal, internalLength,
123       scratchArea, scratchBytes, sourceFile, sourceLine);
124 }
125 
126 template <Direction DIR>
127 Cookie BeginInternalFormattedIO(
128     std::conditional_t<DIR == Direction::Input, const char, char> *internal,
129     std::size_t internalLength, const char *format, std::size_t formatLength,
130     const Descriptor *formatDescriptor, void ** /*scratchArea*/,
131     std::size_t /*scratchBytes*/, const char *sourceFile, int sourceLine) {
132   Terminator oom{sourceFile, sourceLine};
133   return &New<InternalFormattedIoStatementState<DIR>>{oom}(internal,
134       internalLength, format, formatLength, formatDescriptor, sourceFile,
135       sourceLine)
136               .release()
137               ->ioStatementState();
138 }
139 
140 Cookie IONAME(BeginInternalFormattedOutput)(char *internal,
141     std::size_t internalLength, const char *format, std::size_t formatLength,
142     const Descriptor *formatDescriptor, void **scratchArea,
143     std::size_t scratchBytes, const char *sourceFile, int sourceLine) {
144   return BeginInternalFormattedIO<Direction::Output>(internal, internalLength,
145       format, formatLength, formatDescriptor, scratchArea, scratchBytes,
146       sourceFile, sourceLine);
147 }
148 
149 Cookie IONAME(BeginInternalFormattedInput)(const char *internal,
150     std::size_t internalLength, const char *format, std::size_t formatLength,
151     const Descriptor *formatDescriptor, void **scratchArea,
152     std::size_t scratchBytes, const char *sourceFile, int sourceLine) {
153   return BeginInternalFormattedIO<Direction::Input>(internal, internalLength,
154       format, formatLength, formatDescriptor, scratchArea, scratchBytes,
155       sourceFile, sourceLine);
156 }
157 
158 static Cookie NoopUnit(const Terminator &terminator, int unitNumber,
159     enum Iostat iostat = IostatOk) {
160   Cookie cookie{&New<NoopStatementState>{terminator}(
161       terminator.sourceFileName(), terminator.sourceLine(), unitNumber)
162                      .release()
163                      ->ioStatementState()};
164   if (iostat != IostatOk) {
165     cookie->GetIoErrorHandler().SetPendingError(iostat);
166   }
167   return cookie;
168 }
169 
170 static ExternalFileUnit *GetOrCreateUnit(int unitNumber, Direction direction,
171     std::optional<bool> isUnformatted, const Terminator &terminator,
172     Cookie &errorCookie) {
173   if (ExternalFileUnit *
174       unit{ExternalFileUnit::LookUpOrCreateAnonymous(
175           unitNumber, direction, isUnformatted, terminator)}) {
176     errorCookie = nullptr;
177     return unit;
178   } else {
179     errorCookie = NoopUnit(terminator, unitNumber, IostatBadUnitNumber);
180     return nullptr;
181   }
182 }
183 
184 template <Direction DIR, template <Direction> class STATE, typename... A>
185 Cookie BeginExternalListIO(
186     int unitNumber, const char *sourceFile, int sourceLine, A &&...xs) {
187   Terminator terminator{sourceFile, sourceLine};
188   if (unitNumber == DefaultUnit) {
189     unitNumber = DIR == Direction::Input ? 5 : 6;
190   }
191   Cookie errorCookie{nullptr};
192   ExternalFileUnit *unit{GetOrCreateUnit(
193       unitNumber, DIR, false /*!unformatted*/, terminator, errorCookie)};
194   if (!unit) {
195     return errorCookie;
196   }
197   if (!unit->isUnformatted.has_value()) {
198     unit->isUnformatted = false;
199   }
200   Iostat iostat{IostatOk};
201   if (*unit->isUnformatted) {
202     iostat = IostatFormattedIoOnUnformattedUnit;
203   }
204   if (ChildIo * child{unit->GetChildIo()}) {
205     if (iostat == IostatOk) {
206       iostat = child->CheckFormattingAndDirection(false, DIR);
207     }
208     if (iostat == IostatOk) {
209       return &child->BeginIoStatement<ChildListIoStatementState<DIR>>(
210           *child, sourceFile, sourceLine);
211     } else {
212       return &child->BeginIoStatement<ErroneousIoStatementState>(
213           iostat, nullptr /* no unit */, sourceFile, sourceLine);
214     }
215   } else {
216     if (iostat == IostatOk && unit->access == Access::Direct) {
217       iostat = IostatListIoOnDirectAccessUnit;
218     }
219     if (iostat == IostatOk) {
220       iostat = unit->SetDirection(DIR);
221     }
222     if (iostat == IostatOk) {
223       return &unit->BeginIoStatement<STATE<DIR>>(
224           terminator, std::forward<A>(xs)..., *unit, sourceFile, sourceLine);
225     } else {
226       return &unit->BeginIoStatement<ErroneousIoStatementState>(
227           terminator, iostat, unit, sourceFile, sourceLine);
228     }
229   }
230 }
231 
232 Cookie IONAME(BeginExternalListOutput)(
233     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
234   return BeginExternalListIO<Direction::Output, ExternalListIoStatementState>(
235       unitNumber, sourceFile, sourceLine);
236 }
237 
238 Cookie IONAME(BeginExternalListInput)(
239     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
240   return BeginExternalListIO<Direction::Input, ExternalListIoStatementState>(
241       unitNumber, sourceFile, sourceLine);
242 }
243 
244 template <Direction DIR>
245 Cookie BeginExternalFormattedIO(const char *format, std::size_t formatLength,
246     const Descriptor *formatDescriptor, ExternalUnit unitNumber,
247     const char *sourceFile, int sourceLine) {
248   Terminator terminator{sourceFile, sourceLine};
249   if (unitNumber == DefaultUnit) {
250     unitNumber = DIR == Direction::Input ? 5 : 6;
251   }
252   Cookie errorCookie{nullptr};
253   ExternalFileUnit *unit{GetOrCreateUnit(
254       unitNumber, DIR, false /*!unformatted*/, terminator, errorCookie)};
255   if (!unit) {
256     return errorCookie;
257   }
258   Iostat iostat{IostatOk};
259   if (!unit->isUnformatted.has_value()) {
260     unit->isUnformatted = false;
261   }
262   if (*unit->isUnformatted) {
263     iostat = IostatFormattedIoOnUnformattedUnit;
264   }
265   if (ChildIo * child{unit->GetChildIo()}) {
266     if (iostat == IostatOk) {
267       iostat = child->CheckFormattingAndDirection(false, DIR);
268     }
269     if (iostat == IostatOk) {
270       return &child->BeginIoStatement<ChildFormattedIoStatementState<DIR>>(
271           *child, format, formatLength, formatDescriptor, sourceFile,
272           sourceLine);
273     } else {
274       return &child->BeginIoStatement<ErroneousIoStatementState>(
275           iostat, nullptr /* no unit */, sourceFile, sourceLine);
276     }
277   } else {
278     if (iostat == IostatOk) {
279       iostat = unit->SetDirection(DIR);
280     }
281     if (iostat == IostatOk) {
282       return &unit->BeginIoStatement<ExternalFormattedIoStatementState<DIR>>(
283           terminator, *unit, format, formatLength, formatDescriptor, sourceFile,
284           sourceLine);
285     } else {
286       return &unit->BeginIoStatement<ErroneousIoStatementState>(
287           terminator, iostat, unit, sourceFile, sourceLine);
288     }
289   }
290 }
291 
292 Cookie IONAME(BeginExternalFormattedOutput)(const char *format,
293     std::size_t formatLength, const Descriptor *formatDescriptor,
294     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
295   return BeginExternalFormattedIO<Direction::Output>(format, formatLength,
296       formatDescriptor, unitNumber, sourceFile, sourceLine);
297 }
298 
299 Cookie IONAME(BeginExternalFormattedInput)(const char *format,
300     std::size_t formatLength, const Descriptor *formatDescriptor,
301     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
302   return BeginExternalFormattedIO<Direction::Input>(format, formatLength,
303       formatDescriptor, unitNumber, sourceFile, sourceLine);
304 }
305 
306 template <Direction DIR>
307 Cookie BeginUnformattedIO(
308     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
309   Terminator terminator{sourceFile, sourceLine};
310   Cookie errorCookie{nullptr};
311   ExternalFileUnit *unit{GetOrCreateUnit(
312       unitNumber, DIR, true /*unformatted*/, terminator, errorCookie)};
313   if (!unit) {
314     return errorCookie;
315   }
316   Iostat iostat{IostatOk};
317   if (!unit->isUnformatted.has_value()) {
318     unit->isUnformatted = true;
319   }
320   if (!*unit->isUnformatted) {
321     iostat = IostatUnformattedIoOnFormattedUnit;
322   }
323   if (ChildIo * child{unit->GetChildIo()}) {
324     if (iostat == IostatOk) {
325       iostat = child->CheckFormattingAndDirection(true, DIR);
326     }
327     if (iostat == IostatOk) {
328       return &child->BeginIoStatement<ChildUnformattedIoStatementState<DIR>>(
329           *child, sourceFile, sourceLine);
330     } else {
331       return &child->BeginIoStatement<ErroneousIoStatementState>(
332           iostat, nullptr /* no unit */, sourceFile, sourceLine);
333     }
334   } else {
335     if (iostat == IostatOk) {
336       iostat = unit->SetDirection(DIR);
337     }
338     if (iostat == IostatOk) {
339       IoStatementState &io{
340           unit->BeginIoStatement<ExternalUnformattedIoStatementState<DIR>>(
341               terminator, *unit, sourceFile, sourceLine)};
342       if constexpr (DIR == Direction::Output) {
343         if (unit->access == Access::Sequential) {
344           // Create space for (sub)record header to be completed by
345           // ExternalFileUnit::AdvanceRecord()
346           unit->recordLength.reset(); // in case of prior BACKSPACE
347           io.Emit("\0\0\0\0", 4); // placeholder for record length header
348         }
349       }
350       return &io;
351     } else {
352       return &unit->BeginIoStatement<ErroneousIoStatementState>(
353           terminator, iostat, unit, sourceFile, sourceLine);
354     }
355   }
356 }
357 
358 Cookie IONAME(BeginUnformattedOutput)(
359     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
360   return BeginUnformattedIO<Direction::Output>(
361       unitNumber, sourceFile, sourceLine);
362 }
363 
364 Cookie IONAME(BeginUnformattedInput)(
365     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
366   return BeginUnformattedIO<Direction::Input>(
367       unitNumber, sourceFile, sourceLine);
368 }
369 
370 Cookie IONAME(BeginOpenUnit)( // OPEN(without NEWUNIT=)
371     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
372   Terminator terminator{sourceFile, sourceLine};
373   bool wasExtant{false};
374   if (ExternalFileUnit *
375       unit{ExternalFileUnit::LookUpOrCreate(
376           unitNumber, terminator, wasExtant)}) {
377     return &unit->BeginIoStatement<OpenStatementState>(
378         terminator, *unit, wasExtant, sourceFile, sourceLine);
379   } else {
380     return NoopUnit(terminator, unitNumber, IostatBadUnitNumber);
381   }
382 }
383 
384 Cookie IONAME(BeginOpenNewUnit)( // OPEN(NEWUNIT=j)
385     const char *sourceFile, int sourceLine) {
386   Terminator terminator{sourceFile, sourceLine};
387   ExternalFileUnit &unit{
388       ExternalFileUnit::NewUnit(terminator, false /*not child I/O*/)};
389   return &unit.BeginIoStatement<OpenStatementState>(
390       terminator, unit, false /*was an existing file*/, sourceFile, sourceLine);
391 }
392 
393 Cookie IONAME(BeginWait)(ExternalUnit unitNumber, AsynchronousId id,
394     const char *sourceFile, int sourceLine) {
395   Terminator terminator{sourceFile, sourceLine};
396   if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) {
397     if (unit->Wait(id)) {
398       return &unit->BeginIoStatement<ExternalMiscIoStatementState>(terminator,
399           *unit, ExternalMiscIoStatementState::Wait, sourceFile, sourceLine);
400     } else {
401       return &unit->BeginIoStatement<ErroneousIoStatementState>(
402           terminator, IostatBadWaitId, unit, sourceFile, sourceLine);
403     }
404   } else {
405     return NoopUnit(
406         terminator, unitNumber, id == 0 ? IostatOk : IostatBadWaitUnit);
407   }
408 }
409 Cookie IONAME(BeginWaitAll)(
410     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
411   return IONAME(BeginWait)(unitNumber, 0 /*no ID=*/, sourceFile, sourceLine);
412 }
413 
414 Cookie IONAME(BeginClose)(
415     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
416   Terminator terminator{sourceFile, sourceLine};
417   if (ExternalFileUnit * unit{ExternalFileUnit::LookUpForClose(unitNumber)}) {
418     return &unit->BeginIoStatement<CloseStatementState>(
419         terminator, *unit, sourceFile, sourceLine);
420   } else {
421     // CLOSE(UNIT=bad unit) is just a no-op
422     return NoopUnit(terminator, unitNumber);
423   }
424 }
425 
426 Cookie IONAME(BeginFlush)(
427     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
428   Terminator terminator{sourceFile, sourceLine};
429   if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) {
430     return &unit->BeginIoStatement<ExternalMiscIoStatementState>(terminator,
431         *unit, ExternalMiscIoStatementState::Flush, sourceFile, sourceLine);
432   } else {
433     // FLUSH(UNIT=bad unit) is an error; an unconnected unit is a no-op
434     return NoopUnit(terminator, unitNumber,
435         unitNumber >= 0 ? IostatOk : IostatBadFlushUnit);
436   }
437 }
438 
439 Cookie IONAME(BeginBackspace)(
440     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
441   Terminator terminator{sourceFile, sourceLine};
442   if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) {
443     return &unit->BeginIoStatement<ExternalMiscIoStatementState>(terminator,
444         *unit, ExternalMiscIoStatementState::Backspace, sourceFile, sourceLine);
445   } else {
446     return NoopUnit(terminator, unitNumber, IostatBadBackspaceUnit);
447   }
448 }
449 
450 Cookie IONAME(BeginEndfile)(
451     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
452   Terminator terminator{sourceFile, sourceLine};
453   Cookie errorCookie{nullptr};
454   if (ExternalFileUnit *
455       unit{GetOrCreateUnit(unitNumber, Direction::Output, std::nullopt,
456           terminator, errorCookie)}) {
457     return &unit->BeginIoStatement<ExternalMiscIoStatementState>(terminator,
458         *unit, ExternalMiscIoStatementState::Endfile, sourceFile, sourceLine);
459   } else {
460     return errorCookie;
461   }
462 }
463 
464 Cookie IONAME(BeginRewind)(
465     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
466   Terminator terminator{sourceFile, sourceLine};
467   Cookie errorCookie{nullptr};
468   if (ExternalFileUnit *
469       unit{GetOrCreateUnit(unitNumber, Direction::Input, std::nullopt,
470           terminator, errorCookie)}) {
471     return &unit->BeginIoStatement<ExternalMiscIoStatementState>(terminator,
472         *unit, ExternalMiscIoStatementState::Rewind, sourceFile, sourceLine);
473   } else {
474     return errorCookie;
475   }
476 }
477 
478 Cookie IONAME(BeginInquireUnit)(
479     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
480   Terminator terminator{sourceFile, sourceLine};
481   if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) {
482     if (ChildIo * child{unit->GetChildIo()}) {
483       return &child->BeginIoStatement<InquireUnitState>(
484           *unit, sourceFile, sourceLine);
485     } else {
486       return &unit->BeginIoStatement<InquireUnitState>(
487           terminator, *unit, sourceFile, sourceLine);
488     }
489   } else {
490     // INQUIRE(UNIT=unrecognized unit)
491     return &New<InquireNoUnitState>{terminator}(
492         sourceFile, sourceLine, unitNumber)
493                 .release()
494                 ->ioStatementState();
495   }
496 }
497 
498 Cookie IONAME(BeginInquireFile)(const char *path, std::size_t pathLength,
499     const char *sourceFile, int sourceLine) {
500   Terminator terminator{sourceFile, sourceLine};
501   auto trimmed{SaveDefaultCharacter(
502       path, TrimTrailingSpaces(path, pathLength), terminator)};
503   if (ExternalFileUnit *
504       unit{ExternalFileUnit::LookUp(
505           trimmed.get(), std::strlen(trimmed.get()))}) {
506     // INQUIRE(FILE=) to a connected unit
507     return &unit->BeginIoStatement<InquireUnitState>(
508         terminator, *unit, sourceFile, sourceLine);
509   } else {
510     return &New<InquireUnconnectedFileState>{terminator}(
511         std::move(trimmed), sourceFile, sourceLine)
512                 .release()
513                 ->ioStatementState();
514   }
515 }
516 
517 Cookie IONAME(BeginInquireIoLength)(const char *sourceFile, int sourceLine) {
518   Terminator oom{sourceFile, sourceLine};
519   return &New<InquireIOLengthState>{oom}(sourceFile, sourceLine)
520               .release()
521               ->ioStatementState();
522 }
523 
524 // Control list items
525 
526 void IONAME(EnableHandlers)(Cookie cookie, bool hasIoStat, bool hasErr,
527     bool hasEnd, bool hasEor, bool hasIoMsg) {
528   IoErrorHandler &handler{cookie->GetIoErrorHandler()};
529   if (hasIoStat) {
530     handler.HasIoStat();
531   }
532   if (hasErr) {
533     handler.HasErrLabel();
534   }
535   if (hasEnd) {
536     handler.HasEndLabel();
537   }
538   if (hasEor) {
539     handler.HasEorLabel();
540   }
541   if (hasIoMsg) {
542     handler.HasIoMsg();
543   }
544 }
545 
546 static bool YesOrNo(const char *keyword, std::size_t length, const char *what,
547     IoErrorHandler &handler) {
548   static const char *keywords[]{"YES", "NO", nullptr};
549   switch (IdentifyValue(keyword, length, keywords)) {
550   case 0:
551     return true;
552   case 1:
553     return false;
554   default:
555     handler.SignalError(IostatErrorInKeyword, "Invalid %s='%.*s'", what,
556         static_cast<int>(length), keyword);
557     return false;
558   }
559 }
560 
561 bool IONAME(SetAdvance)(
562     Cookie cookie, const char *keyword, std::size_t length) {
563   IoStatementState &io{*cookie};
564   IoErrorHandler &handler{io.GetIoErrorHandler()};
565   bool nonAdvancing{!YesOrNo(keyword, length, "ADVANCE", handler)};
566   if (nonAdvancing && io.GetConnectionState().access == Access::Direct) {
567     handler.SignalError("Non-advancing I/O attempted on direct access file");
568   } else {
569     io.mutableModes().nonAdvancing = nonAdvancing;
570   }
571   return !handler.InError();
572 }
573 
574 bool IONAME(SetBlank)(Cookie cookie, const char *keyword, std::size_t length) {
575   IoStatementState &io{*cookie};
576   static const char *keywords[]{"NULL", "ZERO", nullptr};
577   switch (IdentifyValue(keyword, length, keywords)) {
578   case 0:
579     io.mutableModes().editingFlags &= ~blankZero;
580     return true;
581   case 1:
582     io.mutableModes().editingFlags |= blankZero;
583     return true;
584   default:
585     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
586         "Invalid BLANK='%.*s'", static_cast<int>(length), keyword);
587     return false;
588   }
589 }
590 
591 bool IONAME(SetDecimal)(
592     Cookie cookie, const char *keyword, std::size_t length) {
593   IoStatementState &io{*cookie};
594   static const char *keywords[]{"COMMA", "POINT", nullptr};
595   switch (IdentifyValue(keyword, length, keywords)) {
596   case 0:
597     io.mutableModes().editingFlags |= decimalComma;
598     return true;
599   case 1:
600     io.mutableModes().editingFlags &= ~decimalComma;
601     return true;
602   default:
603     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
604         "Invalid DECIMAL='%.*s'", static_cast<int>(length), keyword);
605     return false;
606   }
607 }
608 
609 bool IONAME(SetDelim)(Cookie cookie, const char *keyword, std::size_t length) {
610   IoStatementState &io{*cookie};
611   static const char *keywords[]{"APOSTROPHE", "QUOTE", "NONE", nullptr};
612   switch (IdentifyValue(keyword, length, keywords)) {
613   case 0:
614     io.mutableModes().delim = '\'';
615     return true;
616   case 1:
617     io.mutableModes().delim = '"';
618     return true;
619   case 2:
620     io.mutableModes().delim = '\0';
621     return true;
622   default:
623     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
624         "Invalid DELIM='%.*s'", static_cast<int>(length), keyword);
625     return false;
626   }
627 }
628 
629 bool IONAME(SetPad)(Cookie cookie, const char *keyword, std::size_t length) {
630   IoStatementState &io{*cookie};
631   IoErrorHandler &handler{io.GetIoErrorHandler()};
632   io.mutableModes().pad = YesOrNo(keyword, length, "PAD", handler);
633   return !handler.InError();
634 }
635 
636 bool IONAME(SetPos)(Cookie cookie, std::int64_t pos) {
637   IoStatementState &io{*cookie};
638   IoErrorHandler &handler{io.GetIoErrorHandler()};
639   if (auto *unit{io.GetExternalFileUnit()}) {
640     return unit->SetStreamPos(pos, handler);
641   } else if (!io.get_if<ErroneousIoStatementState>()) {
642     handler.Crash("SetPos() called on internal unit");
643   }
644   return false;
645 }
646 
647 bool IONAME(SetRec)(Cookie cookie, std::int64_t rec) {
648   IoStatementState &io{*cookie};
649   IoErrorHandler &handler{io.GetIoErrorHandler()};
650   if (auto *unit{io.GetExternalFileUnit()}) {
651     unit->SetDirectRec(rec, handler);
652   } else if (!io.get_if<ErroneousIoStatementState>()) {
653     handler.Crash("SetRec() called on internal unit");
654   }
655   return true;
656 }
657 
658 bool IONAME(SetRound)(Cookie cookie, const char *keyword, std::size_t length) {
659   IoStatementState &io{*cookie};
660   static const char *keywords[]{"UP", "DOWN", "ZERO", "NEAREST", "COMPATIBLE",
661       "PROCESSOR_DEFINED", nullptr};
662   switch (IdentifyValue(keyword, length, keywords)) {
663   case 0:
664     io.mutableModes().round = decimal::RoundUp;
665     return true;
666   case 1:
667     io.mutableModes().round = decimal::RoundDown;
668     return true;
669   case 2:
670     io.mutableModes().round = decimal::RoundToZero;
671     return true;
672   case 3:
673     io.mutableModes().round = decimal::RoundNearest;
674     return true;
675   case 4:
676     io.mutableModes().round = decimal::RoundCompatible;
677     return true;
678   case 5:
679     io.mutableModes().round = executionEnvironment.defaultOutputRoundingMode;
680     return true;
681   default:
682     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
683         "Invalid ROUND='%.*s'", static_cast<int>(length), keyword);
684     return false;
685   }
686 }
687 
688 bool IONAME(SetSign)(Cookie cookie, const char *keyword, std::size_t length) {
689   IoStatementState &io{*cookie};
690   static const char *keywords[]{
691       "PLUS", "SUPPRESS", "PROCESSOR_DEFINED", nullptr};
692   switch (IdentifyValue(keyword, length, keywords)) {
693   case 0:
694     io.mutableModes().editingFlags |= signPlus;
695     return true;
696   case 1:
697   case 2: // processor default is SS
698     io.mutableModes().editingFlags &= ~signPlus;
699     return true;
700   default:
701     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
702         "Invalid SIGN='%.*s'", static_cast<int>(length), keyword);
703     return false;
704   }
705 }
706 
707 bool IONAME(SetAccess)(Cookie cookie, const char *keyword, std::size_t length) {
708   IoStatementState &io{*cookie};
709   auto *open{io.get_if<OpenStatementState>()};
710   if (!open) {
711     if (!io.get_if<ErroneousIoStatementState>()) {
712       io.GetIoErrorHandler().Crash(
713           "SetAccess() called when not in an OPEN statement");
714     }
715     return false;
716   } else if (open->completedOperation()) {
717     io.GetIoErrorHandler().Crash(
718         "SetAccess() called after GetNewUnit() for an OPEN statement");
719   }
720   static const char *keywords[]{
721       "SEQUENTIAL", "DIRECT", "STREAM", "APPEND", nullptr};
722   switch (IdentifyValue(keyword, length, keywords)) {
723   case 0:
724     open->set_access(Access::Sequential);
725     break;
726   case 1:
727     open->set_access(Access::Direct);
728     break;
729   case 2:
730     open->set_access(Access::Stream);
731     break;
732   case 3: // Sun Fortran extension ACCESS=APPEND: treat as if POSITION=APPEND
733     open->set_position(Position::Append);
734     break;
735   default:
736     open->SignalError(IostatErrorInKeyword, "Invalid ACCESS='%.*s'",
737         static_cast<int>(length), keyword);
738   }
739   return true;
740 }
741 
742 bool IONAME(SetAction)(Cookie cookie, const char *keyword, std::size_t length) {
743   IoStatementState &io{*cookie};
744   auto *open{io.get_if<OpenStatementState>()};
745   if (!open) {
746     if (!io.get_if<ErroneousIoStatementState>()) {
747       io.GetIoErrorHandler().Crash(
748           "SetAction() called when not in an OPEN statement");
749     }
750     return false;
751   } else if (open->completedOperation()) {
752     io.GetIoErrorHandler().Crash(
753         "SetAction() called after GetNewUnit() for an OPEN statement");
754   }
755   std::optional<Action> action;
756   static const char *keywords[]{"READ", "WRITE", "READWRITE", nullptr};
757   switch (IdentifyValue(keyword, length, keywords)) {
758   case 0:
759     action = Action::Read;
760     break;
761   case 1:
762     action = Action::Write;
763     break;
764   case 2:
765     action = Action::ReadWrite;
766     break;
767   default:
768     open->SignalError(IostatErrorInKeyword, "Invalid ACTION='%.*s'",
769         static_cast<int>(length), keyword);
770     return false;
771   }
772   RUNTIME_CHECK(io.GetIoErrorHandler(), action.has_value());
773   if (open->wasExtant()) {
774     if ((*action != Action::Write) != open->unit().mayRead() ||
775         (*action != Action::Read) != open->unit().mayWrite()) {
776       open->SignalError("ACTION= may not be changed on an open unit");
777     }
778   }
779   open->set_action(*action);
780   return true;
781 }
782 
783 bool IONAME(SetAsynchronous)(
784     Cookie cookie, const char *keyword, std::size_t length) {
785   IoStatementState &io{*cookie};
786   IoErrorHandler &handler{io.GetIoErrorHandler()};
787   bool isYes{YesOrNo(keyword, length, "ASYNCHRONOUS", handler)};
788   if (auto *open{io.get_if<OpenStatementState>()}) {
789     if (open->completedOperation()) {
790       handler.Crash(
791           "SetAsynchronous() called after GetNewUnit() for an OPEN statement");
792     }
793     open->unit().set_mayAsynchronous(isYes);
794   } else if (auto *ext{io.get_if<ExternalIoStatementBase>()}) {
795     if (isYes) {
796       if (ext->unit().mayAsynchronous()) {
797         ext->SetAsynchronous();
798       } else {
799         handler.SignalError(IostatBadAsynchronous);
800       }
801     }
802   } else if (!io.get_if<ErroneousIoStatementState>()) {
803     handler.Crash("SetAsynchronous() called when not in an OPEN or external "
804                   "I/O statement");
805   }
806   return !handler.InError();
807 }
808 
809 bool IONAME(SetCarriagecontrol)(
810     Cookie cookie, const char *keyword, std::size_t length) {
811   IoStatementState &io{*cookie};
812   auto *open{io.get_if<OpenStatementState>()};
813   if (!open) {
814     if (!io.get_if<ErroneousIoStatementState>()) {
815       io.GetIoErrorHandler().Crash(
816           "SetCarriageControl() called when not in an OPEN statement");
817     }
818     return false;
819   } else if (open->completedOperation()) {
820     io.GetIoErrorHandler().Crash(
821         "SetCarriageControl() called after GetNewUnit() for an OPEN statement");
822   }
823   static const char *keywords[]{"LIST", "FORTRAN", "NONE", nullptr};
824   switch (IdentifyValue(keyword, length, keywords)) {
825   case 0:
826     return true;
827   case 1:
828   case 2:
829     open->SignalError(IostatErrorInKeyword,
830         "Unimplemented CARRIAGECONTROL='%.*s'", static_cast<int>(length),
831         keyword);
832     return false;
833   default:
834     open->SignalError(IostatErrorInKeyword, "Invalid CARRIAGECONTROL='%.*s'",
835         static_cast<int>(length), keyword);
836     return false;
837   }
838 }
839 
840 bool IONAME(SetConvert)(
841     Cookie cookie, const char *keyword, std::size_t length) {
842   IoStatementState &io{*cookie};
843   auto *open{io.get_if<OpenStatementState>()};
844   if (!open) {
845     if (!io.get_if<ErroneousIoStatementState>()) {
846       io.GetIoErrorHandler().Crash(
847           "SetConvert() called when not in an OPEN statement");
848     }
849     return false;
850   } else if (open->completedOperation()) {
851     io.GetIoErrorHandler().Crash(
852         "SetConvert() called after GetNewUnit() for an OPEN statement");
853   }
854   if (auto convert{GetConvertFromString(keyword, length)}) {
855     open->set_convert(*convert);
856     return true;
857   } else {
858     open->SignalError(IostatErrorInKeyword, "Invalid CONVERT='%.*s'",
859         static_cast<int>(length), keyword);
860     return false;
861   }
862 }
863 
864 bool IONAME(SetEncoding)(
865     Cookie cookie, const char *keyword, std::size_t length) {
866   IoStatementState &io{*cookie};
867   auto *open{io.get_if<OpenStatementState>()};
868   if (!open) {
869     if (!io.get_if<ErroneousIoStatementState>()) {
870       io.GetIoErrorHandler().Crash(
871           "SetEncoding() called when not in an OPEN statement");
872     }
873     return false;
874   } else if (open->completedOperation()) {
875     io.GetIoErrorHandler().Crash(
876         "SetEncoding() called after GetNewUnit() for an OPEN statement");
877   }
878   bool isUTF8{false};
879   static const char *keywords[]{"UTF-8", "DEFAULT", nullptr};
880   switch (IdentifyValue(keyword, length, keywords)) {
881   case 0:
882     isUTF8 = true;
883     break;
884   case 1:
885     isUTF8 = false;
886     break;
887   default:
888     open->SignalError(IostatErrorInKeyword, "Invalid ENCODING='%.*s'",
889         static_cast<int>(length), keyword);
890   }
891   if (isUTF8 != open->unit().isUTF8) {
892     if (open->wasExtant()) {
893       open->SignalError("ENCODING= may not be changed on an open unit");
894     }
895     open->unit().isUTF8 = isUTF8;
896   }
897   return true;
898 }
899 
900 bool IONAME(SetForm)(Cookie cookie, const char *keyword, std::size_t length) {
901   IoStatementState &io{*cookie};
902   auto *open{io.get_if<OpenStatementState>()};
903   if (!open) {
904     if (!io.get_if<ErroneousIoStatementState>()) {
905       io.GetIoErrorHandler().Crash(
906           "SetForm() called when not in an OPEN statement");
907     }
908   } else if (open->completedOperation()) {
909     io.GetIoErrorHandler().Crash(
910         "SetForm() called after GetNewUnit() for an OPEN statement");
911   }
912   static const char *keywords[]{"FORMATTED", "UNFORMATTED", nullptr};
913   switch (IdentifyValue(keyword, length, keywords)) {
914   case 0:
915     open->set_isUnformatted(false);
916     break;
917   case 1:
918     open->set_isUnformatted(true);
919     break;
920   default:
921     open->SignalError(IostatErrorInKeyword, "Invalid FORM='%.*s'",
922         static_cast<int>(length), keyword);
923   }
924   return true;
925 }
926 
927 bool IONAME(SetPosition)(
928     Cookie cookie, const char *keyword, std::size_t length) {
929   IoStatementState &io{*cookie};
930   auto *open{io.get_if<OpenStatementState>()};
931   if (!open) {
932     if (!io.get_if<ErroneousIoStatementState>()) {
933       io.GetIoErrorHandler().Crash(
934           "SetPosition() called when not in an OPEN statement");
935     }
936     return false;
937   } else if (open->completedOperation()) {
938     io.GetIoErrorHandler().Crash(
939         "SetPosition() called after GetNewUnit() for an OPEN statement");
940   }
941   static const char *positions[]{"ASIS", "REWIND", "APPEND", nullptr};
942   switch (IdentifyValue(keyword, length, positions)) {
943   case 0:
944     open->set_position(Position::AsIs);
945     return true;
946   case 1:
947     open->set_position(Position::Rewind);
948     return true;
949   case 2:
950     open->set_position(Position::Append);
951     return true;
952   default:
953     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
954         "Invalid POSITION='%.*s'", static_cast<int>(length), keyword);
955   }
956   return true;
957 }
958 
959 bool IONAME(SetRecl)(Cookie cookie, std::size_t n) {
960   IoStatementState &io{*cookie};
961   auto *open{io.get_if<OpenStatementState>()};
962   if (!open) {
963     if (!io.get_if<ErroneousIoStatementState>()) {
964       io.GetIoErrorHandler().Crash(
965           "SetRecl() called when not in an OPEN statement");
966     }
967     return false;
968   } else if (open->completedOperation()) {
969     io.GetIoErrorHandler().Crash(
970         "SetRecl() called after GetNewUnit() for an OPEN statement");
971   }
972   if (n <= 0) {
973     io.GetIoErrorHandler().SignalError("RECL= must be greater than zero");
974     return false;
975   } else if (open->wasExtant() &&
976       open->unit().openRecl.value_or(0) != static_cast<std::int64_t>(n)) {
977     open->SignalError("RECL= may not be changed for an open unit");
978     return false;
979   } else {
980     open->unit().openRecl = n;
981     return true;
982   }
983 }
984 
985 bool IONAME(SetStatus)(Cookie cookie, const char *keyword, std::size_t length) {
986   IoStatementState &io{*cookie};
987   if (auto *open{io.get_if<OpenStatementState>()}) {
988     if (open->completedOperation()) {
989       io.GetIoErrorHandler().Crash(
990           "SetStatus() called after GetNewUnit() for an OPEN statement");
991     }
992     static const char *statuses[]{
993         "OLD", "NEW", "SCRATCH", "REPLACE", "UNKNOWN", nullptr};
994     switch (IdentifyValue(keyword, length, statuses)) {
995     case 0:
996       open->set_status(OpenStatus::Old);
997       return true;
998     case 1:
999       open->set_status(OpenStatus::New);
1000       return true;
1001     case 2:
1002       open->set_status(OpenStatus::Scratch);
1003       return true;
1004     case 3:
1005       open->set_status(OpenStatus::Replace);
1006       return true;
1007     case 4:
1008       open->set_status(OpenStatus::Unknown);
1009       return true;
1010     default:
1011       io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
1012           "Invalid STATUS='%.*s'", static_cast<int>(length), keyword);
1013     }
1014     return false;
1015   }
1016   if (auto *close{io.get_if<CloseStatementState>()}) {
1017     static const char *statuses[]{"KEEP", "DELETE", nullptr};
1018     switch (IdentifyValue(keyword, length, statuses)) {
1019     case 0:
1020       close->set_status(CloseStatus::Keep);
1021       return true;
1022     case 1:
1023       close->set_status(CloseStatus::Delete);
1024       return true;
1025     default:
1026       io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
1027           "Invalid STATUS='%.*s'", static_cast<int>(length), keyword);
1028     }
1029     return false;
1030   }
1031   if (io.get_if<NoopStatementState>() ||
1032       io.get_if<ErroneousIoStatementState>()) {
1033     return true; // don't bother validating STATUS= in a no-op CLOSE
1034   }
1035   io.GetIoErrorHandler().Crash(
1036       "SetStatus() called when not in an OPEN or CLOSE statement");
1037 }
1038 
1039 bool IONAME(SetFile)(Cookie cookie, const char *path, std::size_t chars) {
1040   IoStatementState &io{*cookie};
1041   if (auto *open{io.get_if<OpenStatementState>()}) {
1042     if (open->completedOperation()) {
1043       io.GetIoErrorHandler().Crash(
1044           "SetFile() called after GetNewUnit() for an OPEN statement");
1045     }
1046     open->set_path(path, chars);
1047     return true;
1048   } else if (!io.get_if<ErroneousIoStatementState>()) {
1049     io.GetIoErrorHandler().Crash(
1050         "SetFile() called when not in an OPEN statement");
1051   }
1052   return false;
1053 }
1054 
1055 bool IONAME(GetNewUnit)(Cookie cookie, int &unit, int kind) {
1056   IoStatementState &io{*cookie};
1057   auto *open{io.get_if<OpenStatementState>()};
1058   if (!open) {
1059     if (!io.get_if<ErroneousIoStatementState>()) {
1060       io.GetIoErrorHandler().Crash(
1061           "GetNewUnit() called when not in an OPEN statement");
1062     }
1063     return false;
1064   } else if (!open->InError()) {
1065     open->CompleteOperation();
1066   }
1067   if (open->InError()) {
1068     // A failed OPEN(NEWUNIT=n) does not modify 'n'
1069     return false;
1070   }
1071   std::int64_t result{open->unit().unitNumber()};
1072   if (!SetInteger(unit, kind, result)) {
1073     open->SignalError("GetNewUnit(): bad INTEGER kind(%d) or out-of-range "
1074                       "value(%jd) for result",
1075         kind, static_cast<std::intmax_t>(result));
1076   }
1077   return true;
1078 }
1079 
1080 // Data transfers
1081 
1082 bool IONAME(OutputDescriptor)(Cookie cookie, const Descriptor &descriptor) {
1083   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1084 }
1085 
1086 bool IONAME(InputDescriptor)(Cookie cookie, const Descriptor &descriptor) {
1087   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1088 }
1089 
1090 bool IONAME(OutputUnformattedBlock)(Cookie cookie, const char *x,
1091     std::size_t length, std::size_t elementBytes) {
1092   IoStatementState &io{*cookie};
1093   if (auto *unf{io.get_if<
1094           ExternalUnformattedIoStatementState<Direction::Output>>()}) {
1095     return unf->Emit(x, length, elementBytes);
1096   } else if (auto *inq{io.get_if<InquireIOLengthState>()}) {
1097     return inq->Emit(x, length, elementBytes);
1098   } else if (!io.get_if<ErroneousIoStatementState>()) {
1099     io.GetIoErrorHandler().Crash("OutputUnformattedBlock() called for an I/O "
1100                                  "statement that is not unformatted output");
1101   }
1102   return false;
1103 }
1104 
1105 bool IONAME(InputUnformattedBlock)(
1106     Cookie cookie, char *x, std::size_t length, std::size_t elementBytes) {
1107   IoStatementState &io{*cookie};
1108   IoErrorHandler &handler{io.GetIoErrorHandler()};
1109   io.BeginReadingRecord();
1110   if (handler.InError()) {
1111     return false;
1112   }
1113   if (auto *unf{
1114           io.get_if<ExternalUnformattedIoStatementState<Direction::Input>>()}) {
1115     return unf->Receive(x, length, elementBytes);
1116   } else if (!io.get_if<ErroneousIoStatementState>()) {
1117     handler.Crash("InputUnformattedBlock() called for an I/O statement that is "
1118                   "not unformatted input");
1119   }
1120   return false;
1121 }
1122 
1123 bool IONAME(OutputInteger8)(Cookie cookie, std::int8_t n) {
1124   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputInteger8")) {
1125     return false;
1126   }
1127   StaticDescriptor staticDescriptor;
1128   Descriptor &descriptor{staticDescriptor.descriptor()};
1129   descriptor.Establish(
1130       TypeCategory::Integer, 1, reinterpret_cast<void *>(&n), 0);
1131   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1132 }
1133 
1134 bool IONAME(OutputInteger16)(Cookie cookie, std::int16_t n) {
1135   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputInteger16")) {
1136     return false;
1137   }
1138   StaticDescriptor staticDescriptor;
1139   Descriptor &descriptor{staticDescriptor.descriptor()};
1140   descriptor.Establish(
1141       TypeCategory::Integer, 2, reinterpret_cast<void *>(&n), 0);
1142   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1143 }
1144 
1145 bool IONAME(OutputInteger32)(Cookie cookie, std::int32_t n) {
1146   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputInteger32")) {
1147     return false;
1148   }
1149   StaticDescriptor staticDescriptor;
1150   Descriptor &descriptor{staticDescriptor.descriptor()};
1151   descriptor.Establish(
1152       TypeCategory::Integer, 4, reinterpret_cast<void *>(&n), 0);
1153   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1154 }
1155 
1156 bool IONAME(OutputInteger64)(Cookie cookie, std::int64_t n) {
1157   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputInteger64")) {
1158     return false;
1159   }
1160   StaticDescriptor staticDescriptor;
1161   Descriptor &descriptor{staticDescriptor.descriptor()};
1162   descriptor.Establish(
1163       TypeCategory::Integer, 8, reinterpret_cast<void *>(&n), 0);
1164   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1165 }
1166 
1167 #ifdef __SIZEOF_INT128__
1168 bool IONAME(OutputInteger128)(Cookie cookie, common::int128_t n) {
1169   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputInteger128")) {
1170     return false;
1171   }
1172   StaticDescriptor staticDescriptor;
1173   Descriptor &descriptor{staticDescriptor.descriptor()};
1174   descriptor.Establish(
1175       TypeCategory::Integer, 16, reinterpret_cast<void *>(&n), 0);
1176   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1177 }
1178 #endif
1179 
1180 bool IONAME(InputInteger)(Cookie cookie, std::int64_t &n, int kind) {
1181   if (!cookie->CheckFormattedStmtType<Direction::Input>("InputInteger")) {
1182     return false;
1183   }
1184   StaticDescriptor staticDescriptor;
1185   Descriptor &descriptor{staticDescriptor.descriptor()};
1186   descriptor.Establish(
1187       TypeCategory::Integer, kind, reinterpret_cast<void *>(&n), 0);
1188   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1189 }
1190 
1191 bool IONAME(OutputReal32)(Cookie cookie, float x) {
1192   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputReal32")) {
1193     return false;
1194   }
1195   StaticDescriptor staticDescriptor;
1196   Descriptor &descriptor{staticDescriptor.descriptor()};
1197   descriptor.Establish(TypeCategory::Real, 4, reinterpret_cast<void *>(&x), 0);
1198   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1199 }
1200 
1201 bool IONAME(OutputReal64)(Cookie cookie, double x) {
1202   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputReal64")) {
1203     return false;
1204   }
1205   StaticDescriptor staticDescriptor;
1206   Descriptor &descriptor{staticDescriptor.descriptor()};
1207   descriptor.Establish(TypeCategory::Real, 8, reinterpret_cast<void *>(&x), 0);
1208   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1209 }
1210 
1211 bool IONAME(InputReal32)(Cookie cookie, float &x) {
1212   if (!cookie->CheckFormattedStmtType<Direction::Input>("InputReal32")) {
1213     return false;
1214   }
1215   StaticDescriptor staticDescriptor;
1216   Descriptor &descriptor{staticDescriptor.descriptor()};
1217   descriptor.Establish(TypeCategory::Real, 4, reinterpret_cast<void *>(&x), 0);
1218   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1219 }
1220 
1221 bool IONAME(InputReal64)(Cookie cookie, double &x) {
1222   if (!cookie->CheckFormattedStmtType<Direction::Input>("InputReal64")) {
1223     return false;
1224   }
1225   StaticDescriptor staticDescriptor;
1226   Descriptor &descriptor{staticDescriptor.descriptor()};
1227   descriptor.Establish(TypeCategory::Real, 8, reinterpret_cast<void *>(&x), 0);
1228   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1229 }
1230 
1231 bool IONAME(OutputComplex32)(Cookie cookie, float r, float i) {
1232   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputComplex32")) {
1233     return false;
1234   }
1235   float z[2]{r, i};
1236   StaticDescriptor staticDescriptor;
1237   Descriptor &descriptor{staticDescriptor.descriptor()};
1238   descriptor.Establish(
1239       TypeCategory::Complex, 4, reinterpret_cast<void *>(&z), 0);
1240   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1241 }
1242 
1243 bool IONAME(OutputComplex64)(Cookie cookie, double r, double i) {
1244   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputComplex64")) {
1245     return false;
1246   }
1247   double z[2]{r, i};
1248   StaticDescriptor staticDescriptor;
1249   Descriptor &descriptor{staticDescriptor.descriptor()};
1250   descriptor.Establish(
1251       TypeCategory::Complex, 8, reinterpret_cast<void *>(&z), 0);
1252   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1253 }
1254 
1255 bool IONAME(InputComplex32)(Cookie cookie, float z[2]) {
1256   if (!cookie->CheckFormattedStmtType<Direction::Input>("InputComplex32")) {
1257     return false;
1258   }
1259   StaticDescriptor staticDescriptor;
1260   Descriptor &descriptor{staticDescriptor.descriptor()};
1261   descriptor.Establish(
1262       TypeCategory::Complex, 4, reinterpret_cast<void *>(z), 0);
1263   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1264 }
1265 
1266 bool IONAME(InputComplex64)(Cookie cookie, double z[2]) {
1267   if (!cookie->CheckFormattedStmtType<Direction::Input>("InputComplex64")) {
1268     return false;
1269   }
1270   StaticDescriptor staticDescriptor;
1271   Descriptor &descriptor{staticDescriptor.descriptor()};
1272   descriptor.Establish(
1273       TypeCategory::Complex, 8, reinterpret_cast<void *>(z), 0);
1274   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1275 }
1276 
1277 bool IONAME(OutputCharacter)(
1278     Cookie cookie, const char *x, std::size_t length, int kind) {
1279   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputCharacter")) {
1280     return false;
1281   }
1282   StaticDescriptor staticDescriptor;
1283   Descriptor &descriptor{staticDescriptor.descriptor()};
1284   descriptor.Establish(
1285       kind, length, reinterpret_cast<void *>(const_cast<char *>(x)), 0);
1286   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1287 }
1288 
1289 bool IONAME(OutputAscii)(Cookie cookie, const char *x, std::size_t length) {
1290   return IONAME(OutputCharacter(cookie, x, length, 1));
1291 }
1292 
1293 bool IONAME(InputCharacter)(
1294     Cookie cookie, char *x, std::size_t length, int kind) {
1295   if (!cookie->CheckFormattedStmtType<Direction::Input>("InputCharacter")) {
1296     return false;
1297   }
1298   StaticDescriptor staticDescriptor;
1299   Descriptor &descriptor{staticDescriptor.descriptor()};
1300   descriptor.Establish(kind, length, reinterpret_cast<void *>(x), 0);
1301   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1302 }
1303 
1304 bool IONAME(InputAscii)(Cookie cookie, char *x, std::size_t length) {
1305   return IONAME(InputCharacter)(cookie, x, length, 1);
1306 }
1307 
1308 bool IONAME(OutputLogical)(Cookie cookie, bool truth) {
1309   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputLogical")) {
1310     return false;
1311   }
1312   StaticDescriptor staticDescriptor;
1313   Descriptor &descriptor{staticDescriptor.descriptor()};
1314   descriptor.Establish(
1315       TypeCategory::Logical, sizeof truth, reinterpret_cast<void *>(&truth), 0);
1316   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1317 }
1318 
1319 bool IONAME(InputLogical)(Cookie cookie, bool &truth) {
1320   if (!cookie->CheckFormattedStmtType<Direction::Input>("InputLogical")) {
1321     return false;
1322   }
1323   StaticDescriptor staticDescriptor;
1324   Descriptor &descriptor{staticDescriptor.descriptor()};
1325   descriptor.Establish(
1326       TypeCategory::Logical, sizeof truth, reinterpret_cast<void *>(&truth), 0);
1327   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1328 }
1329 
1330 std::size_t IONAME(GetSize)(Cookie cookie) {
1331   IoStatementState &io{*cookie};
1332   IoErrorHandler &handler{io.GetIoErrorHandler()};
1333   if (!handler.InError()) {
1334     io.CompleteOperation();
1335   }
1336   if (const auto *formatted{
1337           io.get_if<FormattedIoStatementState<Direction::Input>>()}) {
1338     return formatted->GetEditDescriptorChars();
1339   } else if (!io.get_if<ErroneousIoStatementState>()) {
1340     handler.Crash("GetIoSize() called for an I/O statement that is not a "
1341                   "formatted READ()");
1342   }
1343   return 0;
1344 }
1345 
1346 std::size_t IONAME(GetIoLength)(Cookie cookie) {
1347   IoStatementState &io{*cookie};
1348   IoErrorHandler &handler{io.GetIoErrorHandler()};
1349   if (!handler.InError()) {
1350     io.CompleteOperation();
1351   }
1352   if (const auto *inq{io.get_if<InquireIOLengthState>()}) {
1353     return inq->bytes();
1354   } else if (!io.get_if<ErroneousIoStatementState>()) {
1355     handler.Crash("GetIoLength() called for an I/O statement that is not "
1356                   "INQUIRE(IOLENGTH=)");
1357   }
1358   return 0;
1359 }
1360 
1361 void IONAME(GetIoMsg)(Cookie cookie, char *msg, std::size_t length) {
1362   IoStatementState &io{*cookie};
1363   IoErrorHandler &handler{io.GetIoErrorHandler()};
1364   if (!handler.InError()) {
1365     io.CompleteOperation();
1366   }
1367   if (handler.InError()) { // leave "msg" alone when no error
1368     handler.GetIoMsg(msg, length);
1369   }
1370 }
1371 
1372 bool IONAME(InquireCharacter)(Cookie cookie, InquiryKeywordHash inquiry,
1373     char *result, std::size_t length) {
1374   IoStatementState &io{*cookie};
1375   return io.Inquire(inquiry, result, length);
1376 }
1377 
1378 bool IONAME(InquireLogical)(
1379     Cookie cookie, InquiryKeywordHash inquiry, bool &result) {
1380   IoStatementState &io{*cookie};
1381   return io.Inquire(inquiry, result);
1382 }
1383 
1384 bool IONAME(InquirePendingId)(Cookie cookie, std::int64_t id, bool &result) {
1385   IoStatementState &io{*cookie};
1386   return io.Inquire(HashInquiryKeyword("PENDING"), id, result);
1387 }
1388 
1389 bool IONAME(InquireInteger64)(
1390     Cookie cookie, InquiryKeywordHash inquiry, std::int64_t &result, int kind) {
1391   IoStatementState &io{*cookie};
1392   std::int64_t n;
1393   if (io.Inquire(inquiry, n)) {
1394     if (SetInteger(result, kind, n)) {
1395       return true;
1396     }
1397     io.GetIoErrorHandler().SignalError(
1398         "InquireInteger64(): bad INTEGER kind(%d) or out-of-range "
1399         "value(%jd) "
1400         "for result",
1401         kind, static_cast<std::intmax_t>(n));
1402   }
1403   return false;
1404 }
1405 
1406 enum Iostat IONAME(EndIoStatement)(Cookie cookie) {
1407   IoStatementState &io{*cookie};
1408   return static_cast<enum Iostat>(io.EndIoStatement());
1409 }
1410 
1411 template <typename INT>
1412 static enum Iostat CheckUnitNumberInRangeImpl(INT unit, bool handleError,
1413     char *ioMsg, std::size_t ioMsgLength, const char *sourceFile,
1414     int sourceLine) {
1415   static_assert(sizeof(INT) >= sizeof(ExternalUnit),
1416       "only intended to be used when the INT to ExternalUnit conversion is "
1417       "narrowing");
1418   if (unit != static_cast<ExternalUnit>(unit)) {
1419     Terminator oom{sourceFile, sourceLine};
1420     IoErrorHandler errorHandler{oom};
1421     if (handleError) {
1422       errorHandler.HasIoStat();
1423       if (ioMsg) {
1424         errorHandler.HasIoMsg();
1425       }
1426     }
1427     // Only provide the bad unit number in the message if SignalError can print
1428     // it accurately. Otherwise, the generic IostatUnitOverflow message will be
1429     // used.
1430     if (static_cast<std::intmax_t>(unit) == unit) {
1431       errorHandler.SignalError(IostatUnitOverflow,
1432           "UNIT number %jd is out of range", static_cast<std::intmax_t>(unit));
1433     } else {
1434       errorHandler.SignalError(IostatUnitOverflow);
1435     }
1436     if (ioMsg) {
1437       errorHandler.GetIoMsg(ioMsg, ioMsgLength);
1438     }
1439     return static_cast<enum Iostat>(errorHandler.GetIoStat());
1440   }
1441   return IostatOk;
1442 }
1443 
1444 enum Iostat IONAME(CheckUnitNumberInRange64)(std::int64_t unit,
1445     bool handleError, char *ioMsg, std::size_t ioMsgLength,
1446     const char *sourceFile, int sourceLine) {
1447   return CheckUnitNumberInRangeImpl(
1448       unit, handleError, ioMsg, ioMsgLength, sourceFile, sourceLine);
1449 }
1450 
1451 #ifdef __SIZEOF_INT128__
1452 enum Iostat IONAME(CheckUnitNumberInRange128)(common::int128_t unit,
1453     bool handleError, char *ioMsg, std::size_t ioMsgLength,
1454     const char *sourceFile, int sourceLine) {
1455   return CheckUnitNumberInRangeImpl(
1456       unit, handleError, ioMsg, ioMsgLength, sourceFile, sourceLine);
1457 }
1458 #endif
1459 
1460 } // namespace Fortran::runtime::io
1461