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