xref: /llvm-project/flang/runtime/io-api.cpp (revision ce6b9b3b58b6c9e51d87084c916fa7aef81401f1)
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     if (ChildIo * child{unit->GetChildIo()}) {
378       return &child->BeginIoStatement<ErroneousIoStatementState>(
379           IostatBadOpOnChildUnit, nullptr /* no unit */, sourceFile,
380           sourceLine);
381     } else {
382       return &unit->BeginIoStatement<OpenStatementState>(terminator, *unit,
383           wasExtant, false /*not NEWUNIT=*/, sourceFile, sourceLine);
384     }
385   } else {
386     return NoopUnit(terminator, unitNumber, IostatBadUnitNumber);
387   }
388 }
389 
390 Cookie IONAME(BeginOpenNewUnit)( // OPEN(NEWUNIT=j)
391     const char *sourceFile, int sourceLine) {
392   Terminator terminator{sourceFile, sourceLine};
393   ExternalFileUnit &unit{
394       ExternalFileUnit::NewUnit(terminator, false /*not child I/O*/)};
395   return &unit.BeginIoStatement<OpenStatementState>(terminator, unit,
396       false /*was an existing file*/, true /*NEWUNIT=*/, sourceFile,
397       sourceLine);
398 }
399 
400 Cookie IONAME(BeginWait)(ExternalUnit unitNumber, AsynchronousId id,
401     const char *sourceFile, int sourceLine) {
402   Terminator terminator{sourceFile, sourceLine};
403   if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) {
404     if (unit->Wait(id)) {
405       return &unit->BeginIoStatement<ExternalMiscIoStatementState>(terminator,
406           *unit, ExternalMiscIoStatementState::Wait, sourceFile, sourceLine);
407     } else {
408       return &unit->BeginIoStatement<ErroneousIoStatementState>(
409           terminator, IostatBadWaitId, unit, sourceFile, sourceLine);
410     }
411   } else {
412     return NoopUnit(
413         terminator, unitNumber, id == 0 ? IostatOk : IostatBadWaitUnit);
414   }
415 }
416 Cookie IONAME(BeginWaitAll)(
417     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
418   return IONAME(BeginWait)(unitNumber, 0 /*no ID=*/, sourceFile, sourceLine);
419 }
420 
421 Cookie IONAME(BeginClose)(
422     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
423   Terminator terminator{sourceFile, sourceLine};
424   if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) {
425     if (ChildIo * child{unit->GetChildIo()}) {
426       return &child->BeginIoStatement<ErroneousIoStatementState>(
427           IostatBadOpOnChildUnit, nullptr /* no unit */, sourceFile,
428           sourceLine);
429     }
430   }
431   if (ExternalFileUnit * unit{ExternalFileUnit::LookUpForClose(unitNumber)}) {
432     return &unit->BeginIoStatement<CloseStatementState>(
433         terminator, *unit, sourceFile, sourceLine);
434   } else {
435     // CLOSE(UNIT=bad unit) is just a no-op
436     return NoopUnit(terminator, unitNumber);
437   }
438 }
439 
440 Cookie IONAME(BeginFlush)(
441     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
442   Terminator terminator{sourceFile, sourceLine};
443   if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) {
444     if (ChildIo * child{unit->GetChildIo()}) {
445       return &child->BeginIoStatement<ExternalMiscIoStatementState>(
446           *unit, ExternalMiscIoStatementState::Flush, sourceFile, sourceLine);
447     } else {
448       return &unit->BeginIoStatement<ExternalMiscIoStatementState>(terminator,
449           *unit, ExternalMiscIoStatementState::Flush, sourceFile, sourceLine);
450     }
451   } else {
452     // FLUSH(UNIT=bad unit) is an error; an unconnected unit is a no-op
453     return NoopUnit(terminator, unitNumber,
454         unitNumber >= 0 ? IostatOk : IostatBadFlushUnit);
455   }
456 }
457 
458 Cookie IONAME(BeginBackspace)(
459     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
460   Terminator terminator{sourceFile, sourceLine};
461   if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) {
462     if (ChildIo * child{unit->GetChildIo()}) {
463       return &child->BeginIoStatement<ErroneousIoStatementState>(
464           IostatBadOpOnChildUnit, nullptr /* no unit */, sourceFile,
465           sourceLine);
466     } else {
467       return &unit->BeginIoStatement<ExternalMiscIoStatementState>(terminator,
468           *unit, ExternalMiscIoStatementState::Backspace, sourceFile,
469           sourceLine);
470     }
471   } else {
472     return NoopUnit(terminator, unitNumber, IostatBadBackspaceUnit);
473   }
474 }
475 
476 Cookie IONAME(BeginEndfile)(
477     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
478   Terminator terminator{sourceFile, sourceLine};
479   Cookie errorCookie{nullptr};
480   if (ExternalFileUnit *
481       unit{GetOrCreateUnit(unitNumber, Direction::Output, std::nullopt,
482           terminator, errorCookie)}) {
483     if (ChildIo * child{unit->GetChildIo()}) {
484       return &child->BeginIoStatement<ErroneousIoStatementState>(
485           IostatBadOpOnChildUnit, nullptr /* no unit */, sourceFile,
486           sourceLine);
487     } else {
488       return &unit->BeginIoStatement<ExternalMiscIoStatementState>(terminator,
489           *unit, ExternalMiscIoStatementState::Endfile, sourceFile, sourceLine);
490     }
491   } else {
492     return errorCookie;
493   }
494 }
495 
496 Cookie IONAME(BeginRewind)(
497     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
498   Terminator terminator{sourceFile, sourceLine};
499   Cookie errorCookie{nullptr};
500   if (ExternalFileUnit *
501       unit{GetOrCreateUnit(unitNumber, Direction::Input, std::nullopt,
502           terminator, errorCookie)}) {
503     if (ChildIo * child{unit->GetChildIo()}) {
504       return &child->BeginIoStatement<ErroneousIoStatementState>(
505           IostatBadOpOnChildUnit, nullptr /* no unit */, sourceFile,
506           sourceLine);
507     } else {
508       return &unit->BeginIoStatement<ExternalMiscIoStatementState>(terminator,
509           *unit, ExternalMiscIoStatementState::Rewind, sourceFile, sourceLine);
510     }
511   } else {
512     return errorCookie;
513   }
514 }
515 
516 Cookie IONAME(BeginInquireUnit)(
517     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
518   Terminator terminator{sourceFile, sourceLine};
519   if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) {
520     if (ChildIo * child{unit->GetChildIo()}) {
521       return &child->BeginIoStatement<InquireUnitState>(
522           *unit, sourceFile, sourceLine);
523     } else {
524       return &unit->BeginIoStatement<InquireUnitState>(
525           terminator, *unit, sourceFile, sourceLine);
526     }
527   } else {
528     // INQUIRE(UNIT=unrecognized unit)
529     return &New<InquireNoUnitState>{terminator}(
530         sourceFile, sourceLine, unitNumber)
531                 .release()
532                 ->ioStatementState();
533   }
534 }
535 
536 Cookie IONAME(BeginInquireFile)(const char *path, std::size_t pathLength,
537     const char *sourceFile, int sourceLine) {
538   Terminator terminator{sourceFile, sourceLine};
539   auto trimmed{SaveDefaultCharacter(
540       path, TrimTrailingSpaces(path, pathLength), terminator)};
541   if (ExternalFileUnit *
542       unit{ExternalFileUnit::LookUp(
543           trimmed.get(), std::strlen(trimmed.get()))}) {
544     // INQUIRE(FILE=) to a connected unit
545     if (ChildIo * child{unit->GetChildIo()}) {
546       return &child->BeginIoStatement<InquireUnitState>(
547           *unit, sourceFile, sourceLine);
548     } else {
549       return &unit->BeginIoStatement<InquireUnitState>(
550           terminator, *unit, sourceFile, sourceLine);
551     }
552   } else {
553     return &New<InquireUnconnectedFileState>{terminator}(
554         std::move(trimmed), sourceFile, sourceLine)
555                 .release()
556                 ->ioStatementState();
557   }
558 }
559 
560 Cookie IONAME(BeginInquireIoLength)(const char *sourceFile, int sourceLine) {
561   Terminator oom{sourceFile, sourceLine};
562   return &New<InquireIOLengthState>{oom}(sourceFile, sourceLine)
563               .release()
564               ->ioStatementState();
565 }
566 
567 // Control list items
568 
569 void IONAME(EnableHandlers)(Cookie cookie, bool hasIoStat, bool hasErr,
570     bool hasEnd, bool hasEor, bool hasIoMsg) {
571   IoErrorHandler &handler{cookie->GetIoErrorHandler()};
572   if (hasIoStat) {
573     handler.HasIoStat();
574   }
575   if (hasErr) {
576     handler.HasErrLabel();
577   }
578   if (hasEnd) {
579     handler.HasEndLabel();
580   }
581   if (hasEor) {
582     handler.HasEorLabel();
583   }
584   if (hasIoMsg) {
585     handler.HasIoMsg();
586   }
587 }
588 
589 static bool YesOrNo(const char *keyword, std::size_t length, const char *what,
590     IoErrorHandler &handler) {
591   static const char *keywords[]{"YES", "NO", nullptr};
592   switch (IdentifyValue(keyword, length, keywords)) {
593   case 0:
594     return true;
595   case 1:
596     return false;
597   default:
598     handler.SignalError(IostatErrorInKeyword, "Invalid %s='%.*s'", what,
599         static_cast<int>(length), keyword);
600     return false;
601   }
602 }
603 
604 bool IONAME(SetAdvance)(
605     Cookie cookie, const char *keyword, std::size_t length) {
606   IoStatementState &io{*cookie};
607   IoErrorHandler &handler{io.GetIoErrorHandler()};
608   bool nonAdvancing{!YesOrNo(keyword, length, "ADVANCE", handler)};
609   if (nonAdvancing && io.GetConnectionState().access == Access::Direct) {
610     handler.SignalError("Non-advancing I/O attempted on direct access file");
611   } else {
612     auto *unit{io.GetExternalFileUnit()};
613     if (unit && unit->GetChildIo()) {
614       // ADVANCE= is ignored for child I/O (12.6.4.8.3 p3)
615     } else {
616       io.mutableModes().nonAdvancing = nonAdvancing;
617     }
618   }
619   return !handler.InError();
620 }
621 
622 bool IONAME(SetBlank)(Cookie cookie, const char *keyword, std::size_t length) {
623   IoStatementState &io{*cookie};
624   static const char *keywords[]{"NULL", "ZERO", nullptr};
625   switch (IdentifyValue(keyword, length, keywords)) {
626   case 0:
627     io.mutableModes().editingFlags &= ~blankZero;
628     return true;
629   case 1:
630     io.mutableModes().editingFlags |= blankZero;
631     return true;
632   default:
633     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
634         "Invalid BLANK='%.*s'", static_cast<int>(length), keyword);
635     return false;
636   }
637 }
638 
639 bool IONAME(SetDecimal)(
640     Cookie cookie, const char *keyword, std::size_t length) {
641   IoStatementState &io{*cookie};
642   static const char *keywords[]{"COMMA", "POINT", nullptr};
643   switch (IdentifyValue(keyword, length, keywords)) {
644   case 0:
645     io.mutableModes().editingFlags |= decimalComma;
646     return true;
647   case 1:
648     io.mutableModes().editingFlags &= ~decimalComma;
649     return true;
650   default:
651     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
652         "Invalid DECIMAL='%.*s'", static_cast<int>(length), keyword);
653     return false;
654   }
655 }
656 
657 bool IONAME(SetDelim)(Cookie cookie, const char *keyword, std::size_t length) {
658   IoStatementState &io{*cookie};
659   static const char *keywords[]{"APOSTROPHE", "QUOTE", "NONE", nullptr};
660   switch (IdentifyValue(keyword, length, keywords)) {
661   case 0:
662     io.mutableModes().delim = '\'';
663     return true;
664   case 1:
665     io.mutableModes().delim = '"';
666     return true;
667   case 2:
668     io.mutableModes().delim = '\0';
669     return true;
670   default:
671     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
672         "Invalid DELIM='%.*s'", static_cast<int>(length), keyword);
673     return false;
674   }
675 }
676 
677 bool IONAME(SetPad)(Cookie cookie, const char *keyword, std::size_t length) {
678   IoStatementState &io{*cookie};
679   IoErrorHandler &handler{io.GetIoErrorHandler()};
680   io.mutableModes().pad = YesOrNo(keyword, length, "PAD", handler);
681   return !handler.InError();
682 }
683 
684 bool IONAME(SetPos)(Cookie cookie, std::int64_t pos) {
685   IoStatementState &io{*cookie};
686   IoErrorHandler &handler{io.GetIoErrorHandler()};
687   if (auto *unit{io.GetExternalFileUnit()}) {
688     return unit->SetStreamPos(pos, handler);
689   } else if (!io.get_if<ErroneousIoStatementState>()) {
690     handler.Crash("SetPos() called on internal unit");
691   }
692   return false;
693 }
694 
695 bool IONAME(SetRec)(Cookie cookie, std::int64_t rec) {
696   IoStatementState &io{*cookie};
697   IoErrorHandler &handler{io.GetIoErrorHandler()};
698   if (auto *unit{io.GetExternalFileUnit()}) {
699     if (unit->GetChildIo()) {
700       handler.SignalError(
701           IostatBadOpOnChildUnit, "REC= specifier on child I/O");
702     } else {
703       unit->SetDirectRec(rec, handler);
704     }
705   } else if (!io.get_if<ErroneousIoStatementState>()) {
706     handler.Crash("SetRec() called on internal unit");
707   }
708   return true;
709 }
710 
711 bool IONAME(SetRound)(Cookie cookie, const char *keyword, std::size_t length) {
712   IoStatementState &io{*cookie};
713   static const char *keywords[]{"UP", "DOWN", "ZERO", "NEAREST", "COMPATIBLE",
714       "PROCESSOR_DEFINED", nullptr};
715   switch (IdentifyValue(keyword, length, keywords)) {
716   case 0:
717     io.mutableModes().round = decimal::RoundUp;
718     return true;
719   case 1:
720     io.mutableModes().round = decimal::RoundDown;
721     return true;
722   case 2:
723     io.mutableModes().round = decimal::RoundToZero;
724     return true;
725   case 3:
726     io.mutableModes().round = decimal::RoundNearest;
727     return true;
728   case 4:
729     io.mutableModes().round = decimal::RoundCompatible;
730     return true;
731   case 5:
732     io.mutableModes().round = executionEnvironment.defaultOutputRoundingMode;
733     return true;
734   default:
735     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
736         "Invalid ROUND='%.*s'", static_cast<int>(length), keyword);
737     return false;
738   }
739 }
740 
741 bool IONAME(SetSign)(Cookie cookie, const char *keyword, std::size_t length) {
742   IoStatementState &io{*cookie};
743   static const char *keywords[]{
744       "PLUS", "SUPPRESS", "PROCESSOR_DEFINED", nullptr};
745   switch (IdentifyValue(keyword, length, keywords)) {
746   case 0:
747     io.mutableModes().editingFlags |= signPlus;
748     return true;
749   case 1:
750   case 2: // processor default is SS
751     io.mutableModes().editingFlags &= ~signPlus;
752     return true;
753   default:
754     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
755         "Invalid SIGN='%.*s'", static_cast<int>(length), keyword);
756     return false;
757   }
758 }
759 
760 bool IONAME(SetAccess)(Cookie cookie, const char *keyword, std::size_t length) {
761   IoStatementState &io{*cookie};
762   auto *open{io.get_if<OpenStatementState>()};
763   if (!open) {
764     if (!io.get_if<ErroneousIoStatementState>()) {
765       io.GetIoErrorHandler().Crash(
766           "SetAccess() called when not in an OPEN statement");
767     }
768     return false;
769   } else if (open->completedOperation()) {
770     io.GetIoErrorHandler().Crash(
771         "SetAccess() called after GetNewUnit() for an OPEN statement");
772   }
773   static const char *keywords[]{
774       "SEQUENTIAL", "DIRECT", "STREAM", "APPEND", nullptr};
775   switch (IdentifyValue(keyword, length, keywords)) {
776   case 0:
777     open->set_access(Access::Sequential);
778     break;
779   case 1:
780     open->set_access(Access::Direct);
781     break;
782   case 2:
783     open->set_access(Access::Stream);
784     break;
785   case 3: // Sun Fortran extension ACCESS=APPEND: treat as if POSITION=APPEND
786     open->set_position(Position::Append);
787     break;
788   default:
789     open->SignalError(IostatErrorInKeyword, "Invalid ACCESS='%.*s'",
790         static_cast<int>(length), keyword);
791   }
792   return true;
793 }
794 
795 bool IONAME(SetAction)(Cookie cookie, const char *keyword, std::size_t length) {
796   IoStatementState &io{*cookie};
797   auto *open{io.get_if<OpenStatementState>()};
798   if (!open) {
799     if (!io.get_if<ErroneousIoStatementState>()) {
800       io.GetIoErrorHandler().Crash(
801           "SetAction() called when not in an OPEN statement");
802     }
803     return false;
804   } else if (open->completedOperation()) {
805     io.GetIoErrorHandler().Crash(
806         "SetAction() called after GetNewUnit() for an OPEN statement");
807   }
808   std::optional<Action> action;
809   static const char *keywords[]{"READ", "WRITE", "READWRITE", nullptr};
810   switch (IdentifyValue(keyword, length, keywords)) {
811   case 0:
812     action = Action::Read;
813     break;
814   case 1:
815     action = Action::Write;
816     break;
817   case 2:
818     action = Action::ReadWrite;
819     break;
820   default:
821     open->SignalError(IostatErrorInKeyword, "Invalid ACTION='%.*s'",
822         static_cast<int>(length), keyword);
823     return false;
824   }
825   RUNTIME_CHECK(io.GetIoErrorHandler(), action.has_value());
826   if (open->wasExtant()) {
827     if ((*action != Action::Write) != open->unit().mayRead() ||
828         (*action != Action::Read) != open->unit().mayWrite()) {
829       open->SignalError("ACTION= may not be changed on an open unit");
830     }
831   }
832   open->set_action(*action);
833   return true;
834 }
835 
836 bool IONAME(SetAsynchronous)(
837     Cookie cookie, const char *keyword, std::size_t length) {
838   IoStatementState &io{*cookie};
839   IoErrorHandler &handler{io.GetIoErrorHandler()};
840   bool isYes{YesOrNo(keyword, length, "ASYNCHRONOUS", handler)};
841   if (auto *open{io.get_if<OpenStatementState>()}) {
842     if (open->completedOperation()) {
843       handler.Crash(
844           "SetAsynchronous() called after GetNewUnit() for an OPEN statement");
845     }
846     open->unit().set_mayAsynchronous(isYes);
847   } else if (auto *ext{io.get_if<ExternalIoStatementBase>()}) {
848     if (isYes) {
849       if (ext->unit().mayAsynchronous()) {
850         ext->SetAsynchronous();
851       } else {
852         handler.SignalError(IostatBadAsynchronous);
853       }
854     }
855   } else if (!io.get_if<ErroneousIoStatementState>()) {
856     handler.Crash("SetAsynchronous() called when not in an OPEN or external "
857                   "I/O statement");
858   }
859   return !handler.InError();
860 }
861 
862 bool IONAME(SetCarriagecontrol)(
863     Cookie cookie, const char *keyword, std::size_t length) {
864   IoStatementState &io{*cookie};
865   auto *open{io.get_if<OpenStatementState>()};
866   if (!open) {
867     if (!io.get_if<ErroneousIoStatementState>()) {
868       io.GetIoErrorHandler().Crash(
869           "SetCarriageControl() called when not in an OPEN statement");
870     }
871     return false;
872   } else if (open->completedOperation()) {
873     io.GetIoErrorHandler().Crash(
874         "SetCarriageControl() called after GetNewUnit() for an OPEN statement");
875   }
876   static const char *keywords[]{"LIST", "FORTRAN", "NONE", nullptr};
877   switch (IdentifyValue(keyword, length, keywords)) {
878   case 0:
879     return true;
880   case 1:
881   case 2:
882     open->SignalError(IostatErrorInKeyword,
883         "Unimplemented CARRIAGECONTROL='%.*s'", static_cast<int>(length),
884         keyword);
885     return false;
886   default:
887     open->SignalError(IostatErrorInKeyword, "Invalid CARRIAGECONTROL='%.*s'",
888         static_cast<int>(length), keyword);
889     return false;
890   }
891 }
892 
893 bool IONAME(SetConvert)(
894     Cookie cookie, const char *keyword, std::size_t length) {
895   IoStatementState &io{*cookie};
896   auto *open{io.get_if<OpenStatementState>()};
897   if (!open) {
898     if (!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<ErroneousIoStatementState>()) {
923       io.GetIoErrorHandler().Crash(
924           "SetEncoding() called when not in an OPEN statement");
925     }
926     return false;
927   } else if (open->completedOperation()) {
928     io.GetIoErrorHandler().Crash(
929         "SetEncoding() called after GetNewUnit() for an OPEN statement");
930   }
931   // Allow the encoding to be changed on an open unit -- it's
932   // useful and safe.
933   static const char *keywords[]{"UTF-8", "DEFAULT", nullptr};
934   switch (IdentifyValue(keyword, length, keywords)) {
935   case 0:
936     open->unit().isUTF8 = true;
937     break;
938   case 1:
939     open->unit().isUTF8 = false;
940     break;
941   default:
942     open->SignalError(IostatErrorInKeyword, "Invalid ENCODING='%.*s'",
943         static_cast<int>(length), keyword);
944   }
945   return true;
946 }
947 
948 bool IONAME(SetForm)(Cookie cookie, const char *keyword, std::size_t length) {
949   IoStatementState &io{*cookie};
950   auto *open{io.get_if<OpenStatementState>()};
951   if (!open) {
952     if (!io.get_if<ErroneousIoStatementState>()) {
953       io.GetIoErrorHandler().Crash(
954           "SetForm() called when not in an OPEN statement");
955     }
956   } else if (open->completedOperation()) {
957     io.GetIoErrorHandler().Crash(
958         "SetForm() called after GetNewUnit() for an OPEN statement");
959   }
960   static const char *keywords[]{"FORMATTED", "UNFORMATTED", nullptr};
961   switch (IdentifyValue(keyword, length, keywords)) {
962   case 0:
963     open->set_isUnformatted(false);
964     break;
965   case 1:
966     open->set_isUnformatted(true);
967     break;
968   default:
969     open->SignalError(IostatErrorInKeyword, "Invalid FORM='%.*s'",
970         static_cast<int>(length), keyword);
971   }
972   return true;
973 }
974 
975 bool IONAME(SetPosition)(
976     Cookie cookie, const char *keyword, std::size_t length) {
977   IoStatementState &io{*cookie};
978   auto *open{io.get_if<OpenStatementState>()};
979   if (!open) {
980     if (!io.get_if<ErroneousIoStatementState>()) {
981       io.GetIoErrorHandler().Crash(
982           "SetPosition() called when not in an OPEN statement");
983     }
984     return false;
985   } else if (open->completedOperation()) {
986     io.GetIoErrorHandler().Crash(
987         "SetPosition() called after GetNewUnit() for an OPEN statement");
988   }
989   static const char *positions[]{"ASIS", "REWIND", "APPEND", nullptr};
990   switch (IdentifyValue(keyword, length, positions)) {
991   case 0:
992     open->set_position(Position::AsIs);
993     return true;
994   case 1:
995     open->set_position(Position::Rewind);
996     return true;
997   case 2:
998     open->set_position(Position::Append);
999     return true;
1000   default:
1001     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
1002         "Invalid POSITION='%.*s'", static_cast<int>(length), keyword);
1003   }
1004   return true;
1005 }
1006 
1007 bool IONAME(SetRecl)(Cookie cookie, std::size_t n) {
1008   IoStatementState &io{*cookie};
1009   auto *open{io.get_if<OpenStatementState>()};
1010   if (!open) {
1011     if (!io.get_if<ErroneousIoStatementState>()) {
1012       io.GetIoErrorHandler().Crash(
1013           "SetRecl() called when not in an OPEN statement");
1014     }
1015     return false;
1016   } else if (open->completedOperation()) {
1017     io.GetIoErrorHandler().Crash(
1018         "SetRecl() called after GetNewUnit() for an OPEN statement");
1019   }
1020   if (n <= 0) {
1021     io.GetIoErrorHandler().SignalError("RECL= must be greater than zero");
1022     return false;
1023   } else if (open->wasExtant() &&
1024       open->unit().openRecl.value_or(0) != static_cast<std::int64_t>(n)) {
1025     open->SignalError("RECL= may not be changed for an open unit");
1026     return false;
1027   } else {
1028     open->unit().openRecl = n;
1029     return true;
1030   }
1031 }
1032 
1033 bool IONAME(SetStatus)(Cookie cookie, const char *keyword, std::size_t length) {
1034   IoStatementState &io{*cookie};
1035   if (auto *open{io.get_if<OpenStatementState>()}) {
1036     if (open->completedOperation()) {
1037       io.GetIoErrorHandler().Crash(
1038           "SetStatus() called after GetNewUnit() for an OPEN statement");
1039     }
1040     static const char *statuses[]{
1041         "OLD", "NEW", "SCRATCH", "REPLACE", "UNKNOWN", nullptr};
1042     switch (IdentifyValue(keyword, length, statuses)) {
1043     case 0:
1044       open->set_status(OpenStatus::Old);
1045       return true;
1046     case 1:
1047       open->set_status(OpenStatus::New);
1048       return true;
1049     case 2:
1050       open->set_status(OpenStatus::Scratch);
1051       return true;
1052     case 3:
1053       open->set_status(OpenStatus::Replace);
1054       return true;
1055     case 4:
1056       open->set_status(OpenStatus::Unknown);
1057       return true;
1058     default:
1059       io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
1060           "Invalid STATUS='%.*s'", static_cast<int>(length), keyword);
1061     }
1062     return false;
1063   }
1064   if (auto *close{io.get_if<CloseStatementState>()}) {
1065     static const char *statuses[]{"KEEP", "DELETE", nullptr};
1066     switch (IdentifyValue(keyword, length, statuses)) {
1067     case 0:
1068       close->set_status(CloseStatus::Keep);
1069       return true;
1070     case 1:
1071       close->set_status(CloseStatus::Delete);
1072       return true;
1073     default:
1074       io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
1075           "Invalid STATUS='%.*s'", static_cast<int>(length), keyword);
1076     }
1077     return false;
1078   }
1079   if (io.get_if<NoopStatementState>() ||
1080       io.get_if<ErroneousIoStatementState>()) {
1081     return true; // don't bother validating STATUS= in a no-op CLOSE
1082   }
1083   io.GetIoErrorHandler().Crash(
1084       "SetStatus() called when not in an OPEN or CLOSE statement");
1085 }
1086 
1087 bool IONAME(SetFile)(Cookie cookie, const char *path, std::size_t chars) {
1088   IoStatementState &io{*cookie};
1089   if (auto *open{io.get_if<OpenStatementState>()}) {
1090     if (open->completedOperation()) {
1091       io.GetIoErrorHandler().Crash(
1092           "SetFile() called after GetNewUnit() for an OPEN statement");
1093     }
1094     open->set_path(path, chars);
1095     return true;
1096   } else if (!io.get_if<ErroneousIoStatementState>()) {
1097     io.GetIoErrorHandler().Crash(
1098         "SetFile() called when not in an OPEN statement");
1099   }
1100   return false;
1101 }
1102 
1103 bool IONAME(GetNewUnit)(Cookie cookie, int &unit, int kind) {
1104   IoStatementState &io{*cookie};
1105   auto *open{io.get_if<OpenStatementState>()};
1106   if (!open) {
1107     if (!io.get_if<ErroneousIoStatementState>()) {
1108       io.GetIoErrorHandler().Crash(
1109           "GetNewUnit() called when not in an OPEN statement");
1110     }
1111     return false;
1112   } else if (!open->InError()) {
1113     open->CompleteOperation();
1114   }
1115   if (open->InError()) {
1116     // A failed OPEN(NEWUNIT=n) does not modify 'n'
1117     return false;
1118   }
1119   std::int64_t result{open->unit().unitNumber()};
1120   if (!SetInteger(unit, kind, result)) {
1121     open->SignalError("GetNewUnit(): bad INTEGER kind(%d) or out-of-range "
1122                       "value(%jd) for result",
1123         kind, static_cast<std::intmax_t>(result));
1124   }
1125   return true;
1126 }
1127 
1128 // Data transfers
1129 
1130 bool IONAME(OutputDescriptor)(Cookie cookie, const Descriptor &descriptor) {
1131   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1132 }
1133 
1134 bool IONAME(InputDescriptor)(Cookie cookie, const Descriptor &descriptor) {
1135   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1136 }
1137 
1138 bool IONAME(OutputUnformattedBlock)(Cookie cookie, const char *x,
1139     std::size_t length, std::size_t elementBytes) {
1140   IoStatementState &io{*cookie};
1141   if (auto *unf{io.get_if<
1142           ExternalUnformattedIoStatementState<Direction::Output>>()}) {
1143     return unf->Emit(x, length, elementBytes);
1144   } else if (auto *inq{io.get_if<InquireIOLengthState>()}) {
1145     return inq->Emit(x, length, elementBytes);
1146   } else if (!io.get_if<ErroneousIoStatementState>()) {
1147     io.GetIoErrorHandler().Crash("OutputUnformattedBlock() called for an I/O "
1148                                  "statement that is not unformatted output");
1149   }
1150   return false;
1151 }
1152 
1153 bool IONAME(InputUnformattedBlock)(
1154     Cookie cookie, char *x, std::size_t length, std::size_t elementBytes) {
1155   IoStatementState &io{*cookie};
1156   IoErrorHandler &handler{io.GetIoErrorHandler()};
1157   io.BeginReadingRecord();
1158   if (handler.InError()) {
1159     return false;
1160   }
1161   if (auto *unf{
1162           io.get_if<ExternalUnformattedIoStatementState<Direction::Input>>()}) {
1163     return unf->Receive(x, length, elementBytes);
1164   } else if (!io.get_if<ErroneousIoStatementState>()) {
1165     handler.Crash("InputUnformattedBlock() called for an I/O statement that is "
1166                   "not unformatted input");
1167   }
1168   return false;
1169 }
1170 
1171 bool IONAME(OutputInteger8)(Cookie cookie, std::int8_t n) {
1172   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputInteger8")) {
1173     return false;
1174   }
1175   StaticDescriptor staticDescriptor;
1176   Descriptor &descriptor{staticDescriptor.descriptor()};
1177   descriptor.Establish(
1178       TypeCategory::Integer, 1, reinterpret_cast<void *>(&n), 0);
1179   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1180 }
1181 
1182 bool IONAME(OutputInteger16)(Cookie cookie, std::int16_t n) {
1183   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputInteger16")) {
1184     return false;
1185   }
1186   StaticDescriptor staticDescriptor;
1187   Descriptor &descriptor{staticDescriptor.descriptor()};
1188   descriptor.Establish(
1189       TypeCategory::Integer, 2, reinterpret_cast<void *>(&n), 0);
1190   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1191 }
1192 
1193 bool IONAME(OutputInteger32)(Cookie cookie, std::int32_t n) {
1194   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputInteger32")) {
1195     return false;
1196   }
1197   StaticDescriptor staticDescriptor;
1198   Descriptor &descriptor{staticDescriptor.descriptor()};
1199   descriptor.Establish(
1200       TypeCategory::Integer, 4, reinterpret_cast<void *>(&n), 0);
1201   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1202 }
1203 
1204 bool IONAME(OutputInteger64)(Cookie cookie, std::int64_t n) {
1205   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputInteger64")) {
1206     return false;
1207   }
1208   StaticDescriptor staticDescriptor;
1209   Descriptor &descriptor{staticDescriptor.descriptor()};
1210   descriptor.Establish(
1211       TypeCategory::Integer, 8, reinterpret_cast<void *>(&n), 0);
1212   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1213 }
1214 
1215 #ifdef __SIZEOF_INT128__
1216 bool IONAME(OutputInteger128)(Cookie cookie, common::int128_t n) {
1217   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputInteger128")) {
1218     return false;
1219   }
1220   StaticDescriptor staticDescriptor;
1221   Descriptor &descriptor{staticDescriptor.descriptor()};
1222   descriptor.Establish(
1223       TypeCategory::Integer, 16, reinterpret_cast<void *>(&n), 0);
1224   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1225 }
1226 #endif
1227 
1228 bool IONAME(InputInteger)(Cookie cookie, std::int64_t &n, int kind) {
1229   if (!cookie->CheckFormattedStmtType<Direction::Input>("InputInteger")) {
1230     return false;
1231   }
1232   StaticDescriptor staticDescriptor;
1233   Descriptor &descriptor{staticDescriptor.descriptor()};
1234   descriptor.Establish(
1235       TypeCategory::Integer, kind, reinterpret_cast<void *>(&n), 0);
1236   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1237 }
1238 
1239 bool IONAME(OutputReal32)(Cookie cookie, float x) {
1240   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputReal32")) {
1241     return false;
1242   }
1243   StaticDescriptor staticDescriptor;
1244   Descriptor &descriptor{staticDescriptor.descriptor()};
1245   descriptor.Establish(TypeCategory::Real, 4, reinterpret_cast<void *>(&x), 0);
1246   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1247 }
1248 
1249 bool IONAME(OutputReal64)(Cookie cookie, double x) {
1250   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputReal64")) {
1251     return false;
1252   }
1253   StaticDescriptor staticDescriptor;
1254   Descriptor &descriptor{staticDescriptor.descriptor()};
1255   descriptor.Establish(TypeCategory::Real, 8, reinterpret_cast<void *>(&x), 0);
1256   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1257 }
1258 
1259 bool IONAME(InputReal32)(Cookie cookie, float &x) {
1260   if (!cookie->CheckFormattedStmtType<Direction::Input>("InputReal32")) {
1261     return false;
1262   }
1263   StaticDescriptor staticDescriptor;
1264   Descriptor &descriptor{staticDescriptor.descriptor()};
1265   descriptor.Establish(TypeCategory::Real, 4, reinterpret_cast<void *>(&x), 0);
1266   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1267 }
1268 
1269 bool IONAME(InputReal64)(Cookie cookie, double &x) {
1270   if (!cookie->CheckFormattedStmtType<Direction::Input>("InputReal64")) {
1271     return false;
1272   }
1273   StaticDescriptor staticDescriptor;
1274   Descriptor &descriptor{staticDescriptor.descriptor()};
1275   descriptor.Establish(TypeCategory::Real, 8, reinterpret_cast<void *>(&x), 0);
1276   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1277 }
1278 
1279 bool IONAME(OutputComplex32)(Cookie cookie, float r, float i) {
1280   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputComplex32")) {
1281     return false;
1282   }
1283   float z[2]{r, i};
1284   StaticDescriptor staticDescriptor;
1285   Descriptor &descriptor{staticDescriptor.descriptor()};
1286   descriptor.Establish(
1287       TypeCategory::Complex, 4, reinterpret_cast<void *>(&z), 0);
1288   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1289 }
1290 
1291 bool IONAME(OutputComplex64)(Cookie cookie, double r, double i) {
1292   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputComplex64")) {
1293     return false;
1294   }
1295   double z[2]{r, i};
1296   StaticDescriptor staticDescriptor;
1297   Descriptor &descriptor{staticDescriptor.descriptor()};
1298   descriptor.Establish(
1299       TypeCategory::Complex, 8, reinterpret_cast<void *>(&z), 0);
1300   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1301 }
1302 
1303 bool IONAME(InputComplex32)(Cookie cookie, float z[2]) {
1304   if (!cookie->CheckFormattedStmtType<Direction::Input>("InputComplex32")) {
1305     return false;
1306   }
1307   StaticDescriptor staticDescriptor;
1308   Descriptor &descriptor{staticDescriptor.descriptor()};
1309   descriptor.Establish(
1310       TypeCategory::Complex, 4, reinterpret_cast<void *>(z), 0);
1311   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1312 }
1313 
1314 bool IONAME(InputComplex64)(Cookie cookie, double z[2]) {
1315   if (!cookie->CheckFormattedStmtType<Direction::Input>("InputComplex64")) {
1316     return false;
1317   }
1318   StaticDescriptor staticDescriptor;
1319   Descriptor &descriptor{staticDescriptor.descriptor()};
1320   descriptor.Establish(
1321       TypeCategory::Complex, 8, reinterpret_cast<void *>(z), 0);
1322   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1323 }
1324 
1325 bool IONAME(OutputCharacter)(
1326     Cookie cookie, const char *x, std::size_t length, int kind) {
1327   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputCharacter")) {
1328     return false;
1329   }
1330   StaticDescriptor staticDescriptor;
1331   Descriptor &descriptor{staticDescriptor.descriptor()};
1332   descriptor.Establish(
1333       kind, length, reinterpret_cast<void *>(const_cast<char *>(x)), 0);
1334   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1335 }
1336 
1337 bool IONAME(OutputAscii)(Cookie cookie, const char *x, std::size_t length) {
1338   return IONAME(OutputCharacter(cookie, x, length, 1));
1339 }
1340 
1341 bool IONAME(InputCharacter)(
1342     Cookie cookie, char *x, std::size_t length, int kind) {
1343   if (!cookie->CheckFormattedStmtType<Direction::Input>("InputCharacter")) {
1344     return false;
1345   }
1346   StaticDescriptor staticDescriptor;
1347   Descriptor &descriptor{staticDescriptor.descriptor()};
1348   descriptor.Establish(kind, length, reinterpret_cast<void *>(x), 0);
1349   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1350 }
1351 
1352 bool IONAME(InputAscii)(Cookie cookie, char *x, std::size_t length) {
1353   return IONAME(InputCharacter)(cookie, x, length, 1);
1354 }
1355 
1356 bool IONAME(OutputLogical)(Cookie cookie, bool truth) {
1357   if (!cookie->CheckFormattedStmtType<Direction::Output>("OutputLogical")) {
1358     return false;
1359   }
1360   StaticDescriptor staticDescriptor;
1361   Descriptor &descriptor{staticDescriptor.descriptor()};
1362   descriptor.Establish(
1363       TypeCategory::Logical, sizeof truth, reinterpret_cast<void *>(&truth), 0);
1364   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1365 }
1366 
1367 bool IONAME(InputLogical)(Cookie cookie, bool &truth) {
1368   if (!cookie->CheckFormattedStmtType<Direction::Input>("InputLogical")) {
1369     return false;
1370   }
1371   StaticDescriptor staticDescriptor;
1372   Descriptor &descriptor{staticDescriptor.descriptor()};
1373   descriptor.Establish(
1374       TypeCategory::Logical, sizeof truth, reinterpret_cast<void *>(&truth), 0);
1375   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1376 }
1377 
1378 bool IONAME(OutputDerivedType)(Cookie cookie, const Descriptor &descriptor,
1379     const NonTbpDefinedIoTable *table) {
1380   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor, table);
1381 }
1382 
1383 bool IONAME(InputDerivedType)(Cookie cookie, const Descriptor &descriptor,
1384     const NonTbpDefinedIoTable *table) {
1385   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor, table);
1386 }
1387 
1388 std::size_t IONAME(GetSize)(Cookie cookie) {
1389   IoStatementState &io{*cookie};
1390   IoErrorHandler &handler{io.GetIoErrorHandler()};
1391   if (!handler.InError()) {
1392     io.CompleteOperation();
1393   }
1394   if (const auto *formatted{
1395           io.get_if<FormattedIoStatementState<Direction::Input>>()}) {
1396     return formatted->GetEditDescriptorChars();
1397   } else if (!io.get_if<ErroneousIoStatementState>()) {
1398     handler.Crash("GetIoSize() called for an I/O statement that is not a "
1399                   "formatted READ()");
1400   }
1401   return 0;
1402 }
1403 
1404 std::size_t IONAME(GetIoLength)(Cookie cookie) {
1405   IoStatementState &io{*cookie};
1406   IoErrorHandler &handler{io.GetIoErrorHandler()};
1407   if (!handler.InError()) {
1408     io.CompleteOperation();
1409   }
1410   if (const auto *inq{io.get_if<InquireIOLengthState>()}) {
1411     return inq->bytes();
1412   } else if (!io.get_if<ErroneousIoStatementState>()) {
1413     handler.Crash("GetIoLength() called for an I/O statement that is not "
1414                   "INQUIRE(IOLENGTH=)");
1415   }
1416   return 0;
1417 }
1418 
1419 void IONAME(GetIoMsg)(Cookie cookie, char *msg, std::size_t length) {
1420   IoStatementState &io{*cookie};
1421   IoErrorHandler &handler{io.GetIoErrorHandler()};
1422   if (!handler.InError()) {
1423     io.CompleteOperation();
1424   }
1425   if (handler.InError()) { // leave "msg" alone when no error
1426     handler.GetIoMsg(msg, length);
1427   }
1428 }
1429 
1430 bool IONAME(InquireCharacter)(Cookie cookie, InquiryKeywordHash inquiry,
1431     char *result, std::size_t length) {
1432   IoStatementState &io{*cookie};
1433   return io.Inquire(inquiry, result, length);
1434 }
1435 
1436 bool IONAME(InquireLogical)(
1437     Cookie cookie, InquiryKeywordHash inquiry, bool &result) {
1438   IoStatementState &io{*cookie};
1439   return io.Inquire(inquiry, result);
1440 }
1441 
1442 bool IONAME(InquirePendingId)(Cookie cookie, std::int64_t id, bool &result) {
1443   IoStatementState &io{*cookie};
1444   return io.Inquire(HashInquiryKeyword("PENDING"), id, result);
1445 }
1446 
1447 bool IONAME(InquireInteger64)(
1448     Cookie cookie, InquiryKeywordHash inquiry, std::int64_t &result, int kind) {
1449   IoStatementState &io{*cookie};
1450   std::int64_t n{0}; // safe "undefined" value
1451   if (io.Inquire(inquiry, n)) {
1452     if (SetInteger(result, kind, n)) {
1453       return true;
1454     }
1455     io.GetIoErrorHandler().SignalError(
1456         "InquireInteger64(): bad INTEGER kind(%d) or out-of-range "
1457         "value(%jd) for result",
1458         kind, static_cast<std::intmax_t>(n));
1459   }
1460   return false;
1461 }
1462 
1463 enum Iostat IONAME(EndIoStatement)(Cookie cookie) {
1464   IoStatementState &io{*cookie};
1465   return static_cast<enum Iostat>(io.EndIoStatement());
1466 }
1467 
1468 template <typename INT>
1469 static enum Iostat CheckUnitNumberInRangeImpl(INT unit, bool handleError,
1470     char *ioMsg, std::size_t ioMsgLength, const char *sourceFile,
1471     int sourceLine) {
1472   static_assert(sizeof(INT) >= sizeof(ExternalUnit),
1473       "only intended to be used when the INT to ExternalUnit conversion is "
1474       "narrowing");
1475   if (unit != static_cast<ExternalUnit>(unit)) {
1476     Terminator oom{sourceFile, sourceLine};
1477     IoErrorHandler errorHandler{oom};
1478     if (handleError) {
1479       errorHandler.HasIoStat();
1480       if (ioMsg) {
1481         errorHandler.HasIoMsg();
1482       }
1483     }
1484     // Only provide the bad unit number in the message if SignalError can print
1485     // it accurately. Otherwise, the generic IostatUnitOverflow message will be
1486     // used.
1487     if constexpr (sizeof(INT) > sizeof(std::intmax_t)) {
1488       errorHandler.SignalError(IostatUnitOverflow);
1489     } else if (static_cast<std::intmax_t>(unit) == unit) {
1490       errorHandler.SignalError(IostatUnitOverflow,
1491           "UNIT number %jd is out of range", static_cast<std::intmax_t>(unit));
1492     } else {
1493       errorHandler.SignalError(IostatUnitOverflow);
1494     }
1495     if (ioMsg) {
1496       errorHandler.GetIoMsg(ioMsg, ioMsgLength);
1497     }
1498     return static_cast<enum Iostat>(errorHandler.GetIoStat());
1499   }
1500   return IostatOk;
1501 }
1502 
1503 enum Iostat IONAME(CheckUnitNumberInRange64)(std::int64_t unit,
1504     bool handleError, char *ioMsg, std::size_t ioMsgLength,
1505     const char *sourceFile, int sourceLine) {
1506   return CheckUnitNumberInRangeImpl(
1507       unit, handleError, ioMsg, ioMsgLength, sourceFile, sourceLine);
1508 }
1509 
1510 #ifdef __SIZEOF_INT128__
1511 enum Iostat IONAME(CheckUnitNumberInRange128)(common::int128_t unit,
1512     bool handleError, char *ioMsg, std::size_t ioMsgLength,
1513     const char *sourceFile, int sourceLine) {
1514   return CheckUnitNumberInRangeImpl(
1515       unit, handleError, ioMsg, ioMsgLength, sourceFile, sourceLine);
1516 }
1517 #endif
1518 
1519 } // namespace Fortran::runtime::io
1520 
1521 #if defined(_LIBCPP_VERBOSE_ABORT)
1522 // Provide own definition for `std::__libcpp_verbose_abort` to avoid dependency
1523 // on the version provided by libc++.
1524 
1525 void std::__libcpp_verbose_abort(char const *format, ...) {
1526   va_list list;
1527   va_start(list, format);
1528   std::vfprintf(stderr, format, list);
1529   va_end(list);
1530 
1531   std::abort();
1532 }
1533 #endif
1534