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