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