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