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