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