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