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