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