1 //===-- runtime/io-api.cpp --------------------------------------*- C++ -*-===//
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 "io-api.h"
12 #include "edit-input.h"
13 #include "edit-output.h"
14 #include "environment.h"
15 #include "format.h"
16 #include "io-stmt.h"
17 #include "memory.h"
18 #include "terminator.h"
19 #include "tools.h"
20 #include "unit.h"
21 #include <cstdlib>
22 #include <memory>
23 
24 namespace Fortran::runtime::io {
25 
26 const char *InquiryKeywordHashDecode(
27     char *buffer, std::size_t n, InquiryKeywordHash hash) {
28   if (n < 1) {
29     return nullptr;
30   }
31   char *p{buffer + n};
32   *--p = '\0';
33   while (hash > 1) {
34     if (p < buffer) {
35       return nullptr;
36     }
37     *--p = 'A' + (hash % 26);
38     hash /= 26;
39   }
40   return hash == 1 ? p : nullptr;
41 }
42 
43 template <Direction DIR>
44 Cookie BeginInternalArrayListIO(const Descriptor &descriptor,
45     void ** /*scratchArea*/, std::size_t /*scratchBytes*/,
46     const char *sourceFile, int sourceLine) {
47   Terminator oom{sourceFile, sourceLine};
48   return &New<InternalListIoStatementState<DIR>>{oom}(
49       descriptor, sourceFile, sourceLine)
50               .release()
51               ->ioStatementState();
52 }
53 
54 Cookie IONAME(BeginInternalArrayListOutput)(const Descriptor &descriptor,
55     void **scratchArea, std::size_t scratchBytes, const char *sourceFile,
56     int sourceLine) {
57   return BeginInternalArrayListIO<Direction::Output>(
58       descriptor, scratchArea, scratchBytes, sourceFile, sourceLine);
59 }
60 
61 Cookie IONAME(BeginInternalArrayListInput)(const Descriptor &descriptor,
62     void **scratchArea, std::size_t scratchBytes, const char *sourceFile,
63     int sourceLine) {
64   return BeginInternalArrayListIO<Direction::Input>(
65       descriptor, scratchArea, scratchBytes, sourceFile, sourceLine);
66 }
67 
68 template <Direction DIR>
69 Cookie BeginInternalArrayFormattedIO(const Descriptor &descriptor,
70     const char *format, std::size_t formatLength, void ** /*scratchArea*/,
71     std::size_t /*scratchBytes*/, const char *sourceFile, int sourceLine) {
72   Terminator oom{sourceFile, sourceLine};
73   return &New<InternalFormattedIoStatementState<DIR>>{oom}(
74       descriptor, format, formatLength, sourceFile, sourceLine)
75               .release()
76               ->ioStatementState();
77 }
78 
79 Cookie IONAME(BeginInternalArrayFormattedOutput)(const Descriptor &descriptor,
80     const char *format, std::size_t formatLength, void **scratchArea,
81     std::size_t scratchBytes, const char *sourceFile, int sourceLine) {
82   return BeginInternalArrayFormattedIO<Direction::Output>(descriptor, format,
83       formatLength, scratchArea, scratchBytes, sourceFile, sourceLine);
84 }
85 
86 Cookie IONAME(BeginInternalArrayFormattedInput)(const Descriptor &descriptor,
87     const char *format, std::size_t formatLength, void **scratchArea,
88     std::size_t scratchBytes, const char *sourceFile, int sourceLine) {
89   return BeginInternalArrayFormattedIO<Direction::Input>(descriptor, format,
90       formatLength, scratchArea, scratchBytes, sourceFile, sourceLine);
91 }
92 
93 template <Direction DIR>
94 Cookie BeginInternalListIO(
95     std::conditional_t<DIR == Direction::Input, const char, char> *internal,
96     std::size_t internalLength, void ** /*scratchArea*/,
97     std::size_t /*scratchBytes*/, const char *sourceFile, int sourceLine) {
98   Terminator oom{sourceFile, sourceLine};
99   return &New<InternalListIoStatementState<DIR>>{oom}(
100       internal, internalLength, sourceFile, sourceLine)
101               .release()
102               ->ioStatementState();
103 }
104 
105 Cookie IONAME(BeginInternalListOutput)(char *internal,
106     std::size_t internalLength, void **scratchArea, std::size_t scratchBytes,
107     const char *sourceFile, int sourceLine) {
108   return BeginInternalListIO<Direction::Output>(internal, internalLength,
109       scratchArea, scratchBytes, sourceFile, sourceLine);
110 }
111 
112 Cookie IONAME(BeginInternalListInput)(const char *internal,
113     std::size_t internalLength, void **scratchArea, std::size_t scratchBytes,
114     const char *sourceFile, int sourceLine) {
115   return BeginInternalListIO<Direction::Input>(internal, internalLength,
116       scratchArea, scratchBytes, sourceFile, sourceLine);
117 }
118 
119 template <Direction DIR>
120 Cookie BeginInternalFormattedIO(
121     std::conditional_t<DIR == Direction::Input, const char, char> *internal,
122     std::size_t internalLength, const char *format, std::size_t formatLength,
123     void ** /*scratchArea*/, std::size_t /*scratchBytes*/,
124     const char *sourceFile, int sourceLine) {
125   Terminator oom{sourceFile, sourceLine};
126   return &New<InternalFormattedIoStatementState<DIR>>{oom}(
127       internal, internalLength, format, formatLength, sourceFile, sourceLine)
128               .release()
129               ->ioStatementState();
130 }
131 
132 Cookie IONAME(BeginInternalFormattedOutput)(char *internal,
133     std::size_t internalLength, const char *format, std::size_t formatLength,
134     void **scratchArea, std::size_t scratchBytes, const char *sourceFile,
135     int sourceLine) {
136   return BeginInternalFormattedIO<Direction::Output>(internal, internalLength,
137       format, formatLength, scratchArea, scratchBytes, sourceFile, sourceLine);
138 }
139 
140 Cookie IONAME(BeginInternalFormattedInput)(const char *internal,
141     std::size_t internalLength, const char *format, std::size_t formatLength,
142     void **scratchArea, std::size_t scratchBytes, const char *sourceFile,
143     int sourceLine) {
144   return BeginInternalFormattedIO<Direction::Input>(internal, internalLength,
145       format, formatLength, scratchArea, scratchBytes, sourceFile, sourceLine);
146 }
147 
148 template <Direction DIR>
149 Cookie BeginExternalListIO(
150     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
151   Terminator terminator{sourceFile, sourceLine};
152   if (unitNumber == DefaultUnit) {
153     unitNumber = DIR == Direction::Input ? 5 : 6;
154   }
155   ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous(
156       unitNumber, DIR, false /*formatted*/, terminator)};
157   if (unit.access == Access::Direct) {
158     terminator.Crash("List-directed I/O attempted on direct access file");
159     return nullptr;
160   }
161   if (unit.isUnformatted) {
162     terminator.Crash("List-directed I/O attempted on unformatted file");
163     return nullptr;
164   }
165   IoErrorHandler handler{terminator};
166   unit.SetDirection(DIR, handler);
167   IoStatementState &io{unit.BeginIoStatement<ExternalListIoStatementState<DIR>>(
168       unit, sourceFile, sourceLine)};
169   return &io;
170 }
171 
172 Cookie IONAME(BeginExternalListOutput)(
173     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
174   return BeginExternalListIO<Direction::Output>(
175       unitNumber, sourceFile, sourceLine);
176 }
177 
178 Cookie IONAME(BeginExternalListInput)(
179     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
180   return BeginExternalListIO<Direction::Input>(
181       unitNumber, sourceFile, sourceLine);
182 }
183 
184 template <Direction DIR>
185 Cookie BeginExternalFormattedIO(const char *format, std::size_t formatLength,
186     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
187   Terminator terminator{sourceFile, sourceLine};
188   if (unitNumber == DefaultUnit) {
189     unitNumber = DIR == Direction::Input ? 5 : 6;
190   }
191   ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous(
192       unitNumber, DIR, false /*formatted*/, terminator)};
193   if (unit.isUnformatted) {
194     terminator.Crash("Formatted I/O attempted on unformatted file");
195     return nullptr;
196   }
197   IoErrorHandler handler{terminator};
198   unit.SetDirection(DIR, handler);
199   IoStatementState &io{
200       unit.BeginIoStatement<ExternalFormattedIoStatementState<DIR>>(
201           unit, format, formatLength, sourceFile, sourceLine)};
202   return &io;
203 }
204 
205 Cookie IONAME(BeginExternalFormattedOutput)(const char *format,
206     std::size_t formatLength, ExternalUnit unitNumber, const char *sourceFile,
207     int sourceLine) {
208   return BeginExternalFormattedIO<Direction::Output>(
209       format, formatLength, unitNumber, sourceFile, sourceLine);
210 }
211 
212 Cookie IONAME(BeginExternalFormattedInput)(const char *format,
213     std::size_t formatLength, ExternalUnit unitNumber, const char *sourceFile,
214     int sourceLine) {
215   return BeginExternalFormattedIO<Direction::Input>(
216       format, formatLength, unitNumber, sourceFile, sourceLine);
217 }
218 
219 template <Direction DIR>
220 Cookie BeginUnformattedIO(
221     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
222   Terminator terminator{sourceFile, sourceLine};
223   ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous(
224       unitNumber, DIR, true /*unformatted*/, terminator)};
225   if (!unit.isUnformatted) {
226     terminator.Crash("Unformatted output attempted on formatted file");
227   }
228   IoStatementState &io{unit.BeginIoStatement<UnformattedIoStatementState<DIR>>(
229       unit, sourceFile, sourceLine)};
230   IoErrorHandler handler{terminator};
231   unit.SetDirection(DIR, handler);
232   if constexpr (DIR == Direction::Output) {
233     if (unit.access == Access::Sequential && !unit.isFixedRecordLength) {
234       // Create space for (sub)record header to be completed by
235       // UnformattedIoStatementState<Direction::Output>::EndIoStatement()
236       io.Emit("\0\0\0\0", 4); // placeholder for record length header
237     }
238   }
239   return &io;
240 }
241 
242 Cookie IONAME(BeginUnformattedOutput)(
243     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
244   return BeginUnformattedIO<Direction::Output>(
245       unitNumber, sourceFile, sourceLine);
246 }
247 
248 Cookie IONAME(BeginUnformattedInput)(
249     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
250   return BeginUnformattedIO<Direction::Input>(
251       unitNumber, sourceFile, sourceLine);
252 }
253 
254 Cookie IONAME(BeginOpenUnit)( // OPEN(without NEWUNIT=)
255     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
256   bool wasExtant{false};
257   Terminator terminator{sourceFile, sourceLine};
258   ExternalFileUnit &unit{
259       ExternalFileUnit::LookUpOrCreate(unitNumber, terminator, wasExtant)};
260   return &unit.BeginIoStatement<OpenStatementState>(
261       unit, wasExtant, sourceFile, sourceLine);
262 }
263 
264 Cookie IONAME(BeginOpenNewUnit)( // OPEN(NEWUNIT=j)
265     const char *sourceFile, int sourceLine) {
266   Terminator terminator{sourceFile, sourceLine};
267   bool ignored{false};
268   ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreate(
269       ExternalFileUnit::NewUnit(terminator), terminator, ignored)};
270   return &unit.BeginIoStatement<OpenStatementState>(
271       unit, false /*was an existing file*/, sourceFile, sourceLine);
272 }
273 
274 Cookie IONAME(BeginClose)(
275     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
276   if (ExternalFileUnit * unit{ExternalFileUnit::LookUpForClose(unitNumber)}) {
277     return &unit->BeginIoStatement<CloseStatementState>(
278         *unit, sourceFile, sourceLine);
279   } else {
280     // CLOSE(UNIT=bad unit) is just a no-op
281     Terminator oom{sourceFile, sourceLine};
282     return &New<NoopCloseStatementState>{oom}(sourceFile, sourceLine)
283                 .release()
284                 ->ioStatementState();
285   }
286 }
287 
288 Cookie IONAME(BeginFlush)(
289     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
290   Terminator terminator{sourceFile, sourceLine};
291   ExternalFileUnit &unit{
292       ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)};
293   return &unit.BeginIoStatement<ExternalMiscIoStatementState>(
294       unit, ExternalMiscIoStatementState::Flush, sourceFile, sourceLine);
295 }
296 
297 Cookie IONAME(BeginBackspace)(
298     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
299   Terminator terminator{sourceFile, sourceLine};
300   ExternalFileUnit &unit{
301       ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)};
302   return &unit.BeginIoStatement<ExternalMiscIoStatementState>(
303       unit, ExternalMiscIoStatementState::Backspace, sourceFile, sourceLine);
304 }
305 
306 Cookie IONAME(BeginEndfile)(
307     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
308   Terminator terminator{sourceFile, sourceLine};
309   ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous(
310       unitNumber, Direction::Output, true /*formatted*/, terminator)};
311   return &unit.BeginIoStatement<ExternalMiscIoStatementState>(
312       unit, ExternalMiscIoStatementState::Endfile, sourceFile, sourceLine);
313 }
314 
315 Cookie IONAME(BeginRewind)(
316     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
317   Terminator terminator{sourceFile, sourceLine};
318   ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous(
319       unitNumber, Direction::Input, true /*formatted*/, terminator)};
320   return &unit.BeginIoStatement<ExternalMiscIoStatementState>(
321       unit, ExternalMiscIoStatementState::Rewind, sourceFile, sourceLine);
322 }
323 
324 Cookie IONAME(BeginInquireUnit)(
325     ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
326   if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) {
327     return &unit->BeginIoStatement<InquireUnitState>(
328         *unit, sourceFile, sourceLine);
329   } else {
330     // INQUIRE(UNIT=unrecognized unit)
331     Terminator oom{sourceFile, sourceLine};
332     return &New<InquireNoUnitState>{oom}(sourceFile, sourceLine)
333                 .release()
334                 ->ioStatementState();
335   }
336 }
337 
338 Cookie IONAME(BeginInquireFile)(const char *path, std::size_t pathLength,
339     const char *sourceFile, int sourceLine) {
340   Terminator oom{sourceFile, sourceLine};
341   auto trimmed{
342       SaveDefaultCharacter(path, TrimTrailingSpaces(path, pathLength), oom)};
343   if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(trimmed.get())}) {
344     // INQUIRE(FILE=) to a connected unit
345     return &unit->BeginIoStatement<InquireUnitState>(
346         *unit, sourceFile, sourceLine);
347   } else {
348     return &New<InquireUnconnectedFileState>{oom}(
349         std::move(trimmed), sourceFile, sourceLine)
350                 .release()
351                 ->ioStatementState();
352   }
353 }
354 
355 Cookie IONAME(BeginInquireIoLength)(const char *sourceFile, int sourceLine) {
356   Terminator oom{sourceFile, sourceLine};
357   return &New<InquireIOLengthState>{oom}(sourceFile, sourceLine)
358               .release()
359               ->ioStatementState();
360 }
361 
362 // Control list items
363 
364 void IONAME(EnableHandlers)(Cookie cookie, bool hasIoStat, bool hasErr,
365     bool hasEnd, bool hasEor, bool hasIoMsg) {
366   IoErrorHandler &handler{cookie->GetIoErrorHandler()};
367   if (hasIoStat) {
368     handler.HasIoStat();
369   }
370   if (hasErr) {
371     handler.HasErrLabel();
372   }
373   if (hasEnd) {
374     handler.HasEndLabel();
375   }
376   if (hasEor) {
377     handler.HasEorLabel();
378   }
379   if (hasIoMsg) {
380     handler.HasIoMsg();
381   }
382 }
383 
384 static bool YesOrNo(const char *keyword, std::size_t length, const char *what,
385     IoErrorHandler &handler) {
386   static const char *keywords[]{"YES", "NO", nullptr};
387   switch (IdentifyValue(keyword, length, keywords)) {
388   case 0:
389     return true;
390   case 1:
391     return false;
392   default:
393     handler.SignalError(IostatErrorInKeyword, "Invalid %s='%.*s'", what,
394         static_cast<int>(length), keyword);
395     return false;
396   }
397 }
398 
399 bool IONAME(SetAdvance)(
400     Cookie cookie, const char *keyword, std::size_t length) {
401   IoStatementState &io{*cookie};
402   ConnectionState &connection{io.GetConnectionState()};
403   connection.nonAdvancing =
404       !YesOrNo(keyword, length, "ADVANCE", io.GetIoErrorHandler());
405   if (connection.nonAdvancing && connection.access == Access::Direct) {
406     io.GetIoErrorHandler().SignalError(
407         "Non-advancing I/O attempted on direct access file");
408   }
409   return true;
410 }
411 
412 bool IONAME(SetBlank)(Cookie cookie, const char *keyword, std::size_t length) {
413   IoStatementState &io{*cookie};
414   ConnectionState &connection{io.GetConnectionState()};
415   static const char *keywords[]{"NULL", "ZERO", nullptr};
416   switch (IdentifyValue(keyword, length, keywords)) {
417   case 0:
418     connection.modes.editingFlags &= ~blankZero;
419     return true;
420   case 1:
421     connection.modes.editingFlags |= blankZero;
422     return true;
423   default:
424     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
425         "Invalid BLANK='%.*s'", static_cast<int>(length), keyword);
426     return false;
427   }
428 }
429 
430 bool IONAME(SetDecimal)(
431     Cookie cookie, const char *keyword, std::size_t length) {
432   IoStatementState &io{*cookie};
433   ConnectionState &connection{io.GetConnectionState()};
434   static const char *keywords[]{"COMMA", "POINT", nullptr};
435   switch (IdentifyValue(keyword, length, keywords)) {
436   case 0:
437     connection.modes.editingFlags |= decimalComma;
438     return true;
439   case 1:
440     connection.modes.editingFlags &= ~decimalComma;
441     return true;
442   default:
443     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
444         "Invalid DECIMAL='%.*s'", static_cast<int>(length), keyword);
445     return false;
446   }
447 }
448 
449 bool IONAME(SetDelim)(Cookie cookie, const char *keyword, std::size_t length) {
450   IoStatementState &io{*cookie};
451   ConnectionState &connection{io.GetConnectionState()};
452   static const char *keywords[]{"APOSTROPHE", "QUOTE", "NONE", nullptr};
453   switch (IdentifyValue(keyword, length, keywords)) {
454   case 0:
455     connection.modes.delim = '\'';
456     return true;
457   case 1:
458     connection.modes.delim = '"';
459     return true;
460   case 2:
461     connection.modes.delim = '\0';
462     return true;
463   default:
464     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
465         "Invalid DELIM='%.*s'", static_cast<int>(length), keyword);
466     return false;
467   }
468 }
469 
470 bool IONAME(SetPad)(Cookie cookie, const char *keyword, std::size_t length) {
471   IoStatementState &io{*cookie};
472   ConnectionState &connection{io.GetConnectionState()};
473   connection.modes.pad =
474       YesOrNo(keyword, length, "PAD", io.GetIoErrorHandler());
475   return true;
476 }
477 
478 bool IONAME(SetPos)(Cookie cookie, std::int64_t pos) {
479   IoStatementState &io{*cookie};
480   ConnectionState &connection{io.GetConnectionState()};
481   if (connection.access != Access::Stream) {
482     io.GetIoErrorHandler().SignalError(
483         "REC= may not appear unless ACCESS='STREAM'");
484     return false;
485   }
486   if (pos < 1) {
487     io.GetIoErrorHandler().SignalError(
488         "POS=%zd is invalid", static_cast<std::intmax_t>(pos));
489     return false;
490   }
491   if (auto *unit{io.GetExternalFileUnit()}) {
492     unit->SetPosition(pos);
493     return true;
494   }
495   io.GetIoErrorHandler().Crash("SetPos() on internal unit");
496   return false;
497 }
498 
499 bool IONAME(SetRec)(Cookie cookie, std::int64_t rec) {
500   IoStatementState &io{*cookie};
501   ConnectionState &connection{io.GetConnectionState()};
502   if (connection.access != Access::Direct) {
503     io.GetIoErrorHandler().SignalError(
504         "REC= may not appear unless ACCESS='DIRECT'");
505     return false;
506   }
507   if (!connection.isFixedRecordLength || !connection.recordLength) {
508     io.GetIoErrorHandler().SignalError("RECL= was not specified");
509     return false;
510   }
511   if (rec < 1) {
512     io.GetIoErrorHandler().SignalError(
513         "REC=%zd is invalid", static_cast<std::intmax_t>(rec));
514     return false;
515   }
516   connection.currentRecordNumber = rec;
517   if (auto *unit{io.GetExternalFileUnit()}) {
518     unit->SetPosition(rec * *connection.recordLength);
519   }
520   return true;
521 }
522 
523 bool IONAME(SetRound)(Cookie cookie, const char *keyword, std::size_t length) {
524   IoStatementState &io{*cookie};
525   ConnectionState &connection{io.GetConnectionState()};
526   static const char *keywords[]{"UP", "DOWN", "ZERO", "NEAREST", "COMPATIBLE",
527       "PROCESSOR_DEFINED", nullptr};
528   switch (IdentifyValue(keyword, length, keywords)) {
529   case 0:
530     connection.modes.round = decimal::RoundUp;
531     return true;
532   case 1:
533     connection.modes.round = decimal::RoundDown;
534     return true;
535   case 2:
536     connection.modes.round = decimal::RoundToZero;
537     return true;
538   case 3:
539     connection.modes.round = decimal::RoundNearest;
540     return true;
541   case 4:
542     connection.modes.round = decimal::RoundCompatible;
543     return true;
544   case 5:
545     connection.modes.round = executionEnvironment.defaultOutputRoundingMode;
546     return true;
547   default:
548     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
549         "Invalid ROUND='%.*s'", static_cast<int>(length), keyword);
550     return false;
551   }
552 }
553 
554 bool IONAME(SetSign)(Cookie cookie, const char *keyword, std::size_t length) {
555   IoStatementState &io{*cookie};
556   ConnectionState &connection{io.GetConnectionState()};
557   static const char *keywords[]{"PLUS", "YES", "PROCESSOR_DEFINED", nullptr};
558   switch (IdentifyValue(keyword, length, keywords)) {
559   case 0:
560     connection.modes.editingFlags |= signPlus;
561     return true;
562   case 1:
563   case 2: // processor default is SS
564     connection.modes.editingFlags &= ~signPlus;
565     return true;
566   default:
567     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
568         "Invalid SIGN='%.*s'", static_cast<int>(length), keyword);
569     return false;
570   }
571 }
572 
573 bool IONAME(SetAccess)(Cookie cookie, const char *keyword, std::size_t length) {
574   IoStatementState &io{*cookie};
575   auto *open{io.get_if<OpenStatementState>()};
576   if (!open) {
577     io.GetIoErrorHandler().Crash(
578         "SetAccess() called when not in an OPEN statement");
579   }
580   static const char *keywords[]{"SEQUENTIAL", "DIRECT", "STREAM", nullptr};
581   switch (IdentifyValue(keyword, length, keywords)) {
582   case 0:
583     open->set_access(Access::Sequential);
584     break;
585   case 1:
586     open->set_access(Access::Direct);
587     break;
588   case 2:
589     open->set_access(Access::Stream);
590     break;
591   default:
592     open->SignalError(IostatErrorInKeyword, "Invalid ACCESS='%.*s'",
593         static_cast<int>(length), keyword);
594   }
595   return true;
596 }
597 
598 bool IONAME(SetAction)(Cookie cookie, const char *keyword, std::size_t length) {
599   IoStatementState &io{*cookie};
600   auto *open{io.get_if<OpenStatementState>()};
601   if (!open) {
602     io.GetIoErrorHandler().Crash(
603         "SetAction() called when not in an OPEN statement");
604   }
605   std::optional<Action> action;
606   static const char *keywords[]{"READ", "WRITE", "READWRITE", nullptr};
607   switch (IdentifyValue(keyword, length, keywords)) {
608   case 0:
609     action = Action::Read;
610     break;
611   case 1:
612     action = Action::Write;
613     break;
614   case 2:
615     action = Action::ReadWrite;
616     break;
617   default:
618     open->SignalError(IostatErrorInKeyword, "Invalid ACTION='%.*s'",
619         static_cast<int>(length), keyword);
620     return false;
621   }
622   RUNTIME_CHECK(io.GetIoErrorHandler(), action.has_value());
623   if (open->wasExtant()) {
624     if ((*action != Action::Write) != open->unit().mayRead() ||
625         (*action != Action::Read) != open->unit().mayWrite()) {
626       open->SignalError("ACTION= may not be changed on an open unit");
627     }
628   }
629   open->set_action(*action);
630   return true;
631 }
632 
633 bool IONAME(SetAsynchronous)(
634     Cookie cookie, const char *keyword, std::size_t length) {
635   IoStatementState &io{*cookie};
636   auto *open{io.get_if<OpenStatementState>()};
637   if (!open) {
638     io.GetIoErrorHandler().Crash(
639         "SetAsynchronous() called when not in an OPEN statement");
640   }
641   static const char *keywords[]{"YES", "NO", nullptr};
642   switch (IdentifyValue(keyword, length, keywords)) {
643   case 0:
644     open->unit().set_mayAsynchronous(true);
645     return true;
646   case 1:
647     open->unit().set_mayAsynchronous(false);
648     return true;
649   default:
650     open->SignalError(IostatErrorInKeyword, "Invalid ASYNCHRONOUS='%.*s'",
651         static_cast<int>(length), keyword);
652     return false;
653   }
654 }
655 
656 bool IONAME(SetConvert)(
657     Cookie cookie, const char *keyword, std::size_t length) {
658   IoStatementState &io{*cookie};
659   auto *open{io.get_if<OpenStatementState>()};
660   if (!open) {
661     io.GetIoErrorHandler().Crash(
662         "SetConvert() called when not in an OPEN statement");
663   }
664   if (auto convert{GetConvertFromString(keyword, length)}) {
665     open->set_convert(*convert);
666     return true;
667   } else {
668     open->SignalError(IostatErrorInKeyword, "Invalid CONVERT='%.*s'",
669         static_cast<int>(length), keyword);
670     return false;
671   }
672 }
673 
674 bool IONAME(SetEncoding)(
675     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         "SetEncoding() called when not in an OPEN statement");
681   }
682   bool isUTF8{false};
683   static const char *keywords[]{"UTF-8", "DEFAULT", nullptr};
684   switch (IdentifyValue(keyword, length, keywords)) {
685   case 0:
686     isUTF8 = true;
687     break;
688   case 1:
689     isUTF8 = false;
690     break;
691   default:
692     open->SignalError(IostatErrorInKeyword, "Invalid ENCODING='%.*s'",
693         static_cast<int>(length), keyword);
694   }
695   if (isUTF8 != open->unit().isUTF8) {
696     if (open->wasExtant()) {
697       open->SignalError("ENCODING= may not be changed on an open unit");
698     }
699     open->unit().isUTF8 = isUTF8;
700   }
701   return true;
702 }
703 
704 bool IONAME(SetForm)(Cookie cookie, const char *keyword, std::size_t length) {
705   IoStatementState &io{*cookie};
706   auto *open{io.get_if<OpenStatementState>()};
707   if (!open) {
708     io.GetIoErrorHandler().Crash(
709         "SetEncoding() called when not in an OPEN statement");
710   }
711   static const char *keywords[]{"FORMATTED", "UNFORMATTED", nullptr};
712   switch (IdentifyValue(keyword, length, keywords)) {
713   case 0:
714     open->set_isUnformatted(false);
715     break;
716   case 1:
717     open->set_isUnformatted(true);
718     break;
719   default:
720     open->SignalError(IostatErrorInKeyword, "Invalid FORM='%.*s'",
721         static_cast<int>(length), keyword);
722   }
723   return true;
724 }
725 
726 bool IONAME(SetPosition)(
727     Cookie cookie, const char *keyword, std::size_t length) {
728   IoStatementState &io{*cookie};
729   auto *open{io.get_if<OpenStatementState>()};
730   if (!open) {
731     io.GetIoErrorHandler().Crash(
732         "SetPosition() called when not in an OPEN statement");
733   }
734   static const char *positions[]{"ASIS", "REWIND", "APPEND", nullptr};
735   switch (IdentifyValue(keyword, length, positions)) {
736   case 0:
737     open->set_position(Position::AsIs);
738     return true;
739   case 1:
740     open->set_position(Position::Rewind);
741     return true;
742   case 2:
743     open->set_position(Position::Append);
744     return true;
745   default:
746     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
747         "Invalid POSITION='%.*s'", static_cast<int>(length), keyword);
748   }
749   return true;
750 }
751 
752 bool IONAME(SetRecl)(Cookie cookie, std::size_t n) {
753   IoStatementState &io{*cookie};
754   auto *open{io.get_if<OpenStatementState>()};
755   if (!open) {
756     io.GetIoErrorHandler().Crash(
757         "SetRecl() called when not in an OPEN statement");
758   }
759   if (n <= 0) {
760     io.GetIoErrorHandler().SignalError("RECL= must be greater than zero");
761   }
762   if (open->wasExtant() && open->unit().isFixedRecordLength &&
763       open->unit().recordLength.value_or(n) != static_cast<std::int64_t>(n)) {
764     open->SignalError("RECL= may not be changed for an open unit");
765   }
766   open->unit().isFixedRecordLength = true;
767   open->unit().recordLength = n;
768   return true;
769 }
770 
771 bool IONAME(SetStatus)(Cookie cookie, const char *keyword, std::size_t length) {
772   IoStatementState &io{*cookie};
773   if (auto *open{io.get_if<OpenStatementState>()}) {
774     static const char *statuses[]{
775         "OLD", "NEW", "SCRATCH", "REPLACE", "UNKNOWN", nullptr};
776     switch (IdentifyValue(keyword, length, statuses)) {
777     case 0:
778       open->set_status(OpenStatus::Old);
779       return true;
780     case 1:
781       open->set_status(OpenStatus::New);
782       return true;
783     case 2:
784       open->set_status(OpenStatus::Scratch);
785       return true;
786     case 3:
787       open->set_status(OpenStatus::Replace);
788       return true;
789     case 4:
790       open->set_status(OpenStatus::Unknown);
791       return true;
792     default:
793       io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
794           "Invalid STATUS='%.*s'", static_cast<int>(length), keyword);
795     }
796     return false;
797   }
798   if (auto *close{io.get_if<CloseStatementState>()}) {
799     static const char *statuses[]{"KEEP", "DELETE", nullptr};
800     switch (IdentifyValue(keyword, length, statuses)) {
801     case 0:
802       close->set_status(CloseStatus::Keep);
803       return true;
804     case 1:
805       close->set_status(CloseStatus::Delete);
806       return true;
807     default:
808       io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
809           "Invalid STATUS='%.*s'", static_cast<int>(length), keyword);
810     }
811     return false;
812   }
813   if (io.get_if<NoopCloseStatementState>()) {
814     return true; // don't bother validating STATUS= in a no-op CLOSE
815   }
816   io.GetIoErrorHandler().Crash(
817       "SetStatus() called when not in an OPEN or CLOSE statement");
818 }
819 
820 bool IONAME(SetFile)(Cookie cookie, const char *path, std::size_t chars) {
821   IoStatementState &io{*cookie};
822   if (auto *open{io.get_if<OpenStatementState>()}) {
823     open->set_path(path, chars);
824     return true;
825   }
826   io.GetIoErrorHandler().Crash(
827       "SetFile() called when not in an OPEN statement");
828   return false;
829 }
830 
831 template <typename INT>
832 static bool SetInteger(INT &x, int kind, std::int64_t value) {
833   switch (kind) {
834   case 1:
835     reinterpret_cast<std::int8_t &>(x) = value;
836     return true;
837   case 2:
838     reinterpret_cast<std::int16_t &>(x) = value;
839     return true;
840   case 4:
841     reinterpret_cast<std::int32_t &>(x) = value;
842     return true;
843   case 8:
844     reinterpret_cast<std::int64_t &>(x) = value;
845     return true;
846   default:
847     return false;
848   }
849 }
850 
851 bool IONAME(GetNewUnit)(Cookie cookie, int &unit, int kind) {
852   IoStatementState &io{*cookie};
853   auto *open{io.get_if<OpenStatementState>()};
854   if (!open) {
855     io.GetIoErrorHandler().Crash(
856         "GetNewUnit() called when not in an OPEN statement");
857   }
858   if (!SetInteger(unit, kind, open->unit().unitNumber())) {
859     open->SignalError("GetNewUnit(): Bad INTEGER kind(%d) for result");
860   }
861   return true;
862 }
863 
864 // Data transfers
865 
866 bool IONAME(OutputDescriptor)(Cookie cookie, const Descriptor &) {
867   IoStatementState &io{*cookie};
868   io.GetIoErrorHandler().Crash("OutputDescriptor: not yet implemented"); // TODO
869 }
870 
871 bool IONAME(InputDescriptor)(Cookie cookie, const Descriptor &) {
872   IoStatementState &io{*cookie};
873   io.BeginReadingRecord();
874   io.GetIoErrorHandler().Crash("InputDescriptor: not yet implemented"); // TODO
875 }
876 
877 bool IONAME(OutputUnformattedBlock)(Cookie cookie, const char *x,
878     std::size_t length, std::size_t elementBytes) {
879   IoStatementState &io{*cookie};
880   if (auto *unf{io.get_if<UnformattedIoStatementState<Direction::Output>>()}) {
881     return unf->Emit(x, length, elementBytes);
882   }
883   io.GetIoErrorHandler().Crash("OutputUnformattedBlock() called for an I/O "
884                                "statement that is not unformatted output");
885   return false;
886 }
887 
888 bool IONAME(InputUnformattedBlock)(
889     Cookie cookie, char *x, std::size_t length, std::size_t elementBytes) {
890   IoStatementState &io{*cookie};
891   io.BeginReadingRecord();
892   if (auto *unf{io.get_if<UnformattedIoStatementState<Direction::Input>>()}) {
893     return unf->Receive(x, length, elementBytes);
894   }
895   io.GetIoErrorHandler().Crash("InputUnformattedBlock() called for an I/O "
896                                "statement that is not unformatted output");
897   return false;
898 }
899 
900 bool IONAME(OutputInteger64)(Cookie cookie, std::int64_t n) {
901   IoStatementState &io{*cookie};
902   if (!io.get_if<OutputStatementState>()) {
903     io.GetIoErrorHandler().Crash(
904         "OutputInteger64() called for a non-output I/O statement");
905     return false;
906   }
907   if (auto edit{io.GetNextDataEdit()}) {
908     return EditIntegerOutput(io, *edit, n);
909   }
910   return false;
911 }
912 
913 bool IONAME(InputInteger)(Cookie cookie, std::int64_t &n, int kind) {
914   IoStatementState &io{*cookie};
915   if (!io.get_if<InputStatementState>()) {
916     io.GetIoErrorHandler().Crash(
917         "InputInteger64() called for a non-input I/O statement");
918     return false;
919   }
920   io.BeginReadingRecord();
921   if (auto edit{io.GetNextDataEdit()}) {
922     if (edit->descriptor == DataEdit::ListDirectedNullValue) {
923       return true;
924     }
925     return EditIntegerInput(io, *edit, reinterpret_cast<void *>(&n), kind);
926   }
927   return false;
928 }
929 
930 template <int PREC, typename REAL>
931 static bool OutputReal(Cookie cookie, REAL x) {
932   IoStatementState &io{*cookie};
933   if (!io.get_if<OutputStatementState>()) {
934     io.GetIoErrorHandler().Crash(
935         "OutputReal() called for a non-output I/O statement");
936     return false;
937   }
938   if (auto edit{io.GetNextDataEdit()}) {
939     return RealOutputEditing<PREC>{io, x}.Edit(*edit);
940   }
941   return false;
942 }
943 
944 bool IONAME(OutputReal32)(Cookie cookie, float x) {
945   return OutputReal<24, float>(cookie, x);
946 }
947 
948 bool IONAME(OutputReal64)(Cookie cookie, double x) {
949   return OutputReal<53, double>(cookie, x);
950 }
951 
952 template <int PREC, typename REAL>
953 static bool InputReal(Cookie cookie, REAL &x) {
954   IoStatementState &io{*cookie};
955   if (!io.get_if<InputStatementState>()) {
956     io.GetIoErrorHandler().Crash(
957         "InputReal() called for a non-input I/O statement");
958     return false;
959   }
960   io.BeginReadingRecord();
961   if (auto edit{io.GetNextDataEdit()}) {
962     if (edit->descriptor == DataEdit::ListDirectedNullValue) {
963       return true;
964     }
965     return EditRealInput<PREC>(io, *edit, reinterpret_cast<void *>(&x));
966   }
967   return false;
968 }
969 
970 bool IONAME(InputReal32)(Cookie cookie, float &x) {
971   return InputReal<24, float>(cookie, x);
972 }
973 
974 bool IONAME(InputReal64)(Cookie cookie, double &x) {
975   return InputReal<53, double>(cookie, x);
976 }
977 
978 template <int PREC, typename REAL>
979 static bool OutputComplex(Cookie cookie, REAL r, REAL z) {
980   IoStatementState &io{*cookie};
981   if (io.get_if<ListDirectedStatementState<Direction::Output>>()) {
982     DataEdit real, imaginary;
983     real.descriptor = DataEdit::ListDirectedRealPart;
984     imaginary.descriptor = DataEdit::ListDirectedImaginaryPart;
985     return RealOutputEditing<PREC>{io, r}.Edit(real) &&
986         RealOutputEditing<PREC>{io, z}.Edit(imaginary);
987   }
988   return OutputReal<PREC, REAL>(cookie, r) && OutputReal<PREC, REAL>(cookie, z);
989 }
990 
991 bool IONAME(OutputComplex32)(Cookie cookie, float r, float z) {
992   return OutputComplex<24, float>(cookie, r, z);
993 }
994 
995 bool IONAME(OutputComplex64)(Cookie cookie, double r, double z) {
996   return OutputComplex<53, double>(cookie, r, z);
997 }
998 
999 template <int PREC, typename REAL>
1000 static bool InputComplex(Cookie cookie, REAL x[2]) {
1001   IoStatementState &io{*cookie};
1002   if (!io.get_if<InputStatementState>()) {
1003     io.GetIoErrorHandler().Crash(
1004         "InputComplex() called for a non-input I/O statement");
1005     return false;
1006   }
1007   io.BeginReadingRecord();
1008   for (int j{0}; j < 2; ++j) {
1009     if (auto edit{io.GetNextDataEdit()}) {
1010       if (edit->descriptor == DataEdit::ListDirectedNullValue) {
1011         return true;
1012       }
1013       if (!EditRealInput<PREC>(io, *edit, reinterpret_cast<void *>(&x[j]))) {
1014         return false;
1015       }
1016     }
1017   }
1018   return true;
1019 }
1020 
1021 bool IONAME(InputComplex32)(Cookie cookie, float x[2]) {
1022   return InputComplex<24, float>(cookie, x);
1023 }
1024 
1025 bool IONAME(InputComplex64)(Cookie cookie, double x[2]) {
1026   return InputComplex<53, double>(cookie, x);
1027 }
1028 
1029 bool IONAME(OutputAscii)(Cookie cookie, const char *x, std::size_t length) {
1030   IoStatementState &io{*cookie};
1031   if (!io.get_if<OutputStatementState>()) {
1032     io.GetIoErrorHandler().Crash(
1033         "OutputAscii() called for a non-output I/O statement");
1034     return false;
1035   }
1036   if (auto *list{io.get_if<ListDirectedStatementState<Direction::Output>>()}) {
1037     return ListDirectedDefaultCharacterOutput(io, *list, x, length);
1038   } else if (auto edit{io.GetNextDataEdit()}) {
1039     return EditDefaultCharacterOutput(io, *edit, x, length);
1040   } else {
1041     return false;
1042   }
1043 }
1044 
1045 bool IONAME(InputAscii)(Cookie cookie, char *x, std::size_t length) {
1046   IoStatementState &io{*cookie};
1047   if (!io.get_if<InputStatementState>()) {
1048     io.GetIoErrorHandler().Crash(
1049         "InputAscii() called for a non-input I/O statement");
1050     return false;
1051   }
1052   io.BeginReadingRecord();
1053   if (auto edit{io.GetNextDataEdit()}) {
1054     if (edit->descriptor == DataEdit::ListDirectedNullValue) {
1055       return true;
1056     }
1057     return EditDefaultCharacterInput(io, *edit, x, length);
1058   }
1059   return false;
1060 }
1061 
1062 bool IONAME(OutputLogical)(Cookie cookie, bool truth) {
1063   IoStatementState &io{*cookie};
1064   if (!io.get_if<OutputStatementState>()) {
1065     io.GetIoErrorHandler().Crash(
1066         "OutputLogical() called for a non-output I/O statement");
1067     return false;
1068   }
1069   if (auto *list{io.get_if<ListDirectedStatementState<Direction::Output>>()}) {
1070     return ListDirectedLogicalOutput(io, *list, truth);
1071   } else if (auto edit{io.GetNextDataEdit()}) {
1072     return EditLogicalOutput(io, *edit, truth);
1073   } else {
1074     return false;
1075   }
1076 }
1077 
1078 bool IONAME(InputLogical)(Cookie cookie, bool &truth) {
1079   IoStatementState &io{*cookie};
1080   if (!io.get_if<InputStatementState>()) {
1081     io.GetIoErrorHandler().Crash(
1082         "InputLogical() called for a non-input I/O statement");
1083     return false;
1084   }
1085   io.BeginReadingRecord();
1086   if (auto edit{io.GetNextDataEdit()}) {
1087     if (edit->descriptor == DataEdit::ListDirectedNullValue) {
1088       return true;
1089     }
1090     return EditLogicalInput(io, *edit, truth);
1091   }
1092   return false;
1093 }
1094 
1095 void IONAME(GetIoMsg)(Cookie cookie, char *msg, std::size_t length) {
1096   IoErrorHandler &handler{cookie->GetIoErrorHandler()};
1097   if (handler.GetIoStat()) { // leave "msg" alone when no error
1098     handler.GetIoMsg(msg, length);
1099   }
1100 }
1101 
1102 bool IONAME(InquireCharacter)(Cookie cookie, InquiryKeywordHash inquiry,
1103     char *result, std::size_t length) {
1104   IoStatementState &io{*cookie};
1105   return io.Inquire(inquiry, result, length);
1106 }
1107 
1108 bool IONAME(InquireLogical)(
1109     Cookie cookie, InquiryKeywordHash inquiry, bool &result) {
1110   IoStatementState &io{*cookie};
1111   return io.Inquire(inquiry, result);
1112 }
1113 
1114 bool IONAME(InquirePendingId)(Cookie cookie, std::int64_t id, bool &result) {
1115   IoStatementState &io{*cookie};
1116   return io.Inquire(HashInquiryKeyword("PENDING"), id, result);
1117 }
1118 
1119 bool IONAME(InquireInteger64)(
1120     Cookie cookie, InquiryKeywordHash inquiry, std::int64_t &result, int kind) {
1121   IoStatementState &io{*cookie};
1122   std::int64_t n;
1123   if (io.Inquire(inquiry, n)) {
1124     SetInteger(result, kind, n);
1125     return true;
1126   }
1127   return false;
1128 }
1129 
1130 enum Iostat IONAME(EndIoStatement)(Cookie cookie) {
1131   IoStatementState &io{*cookie};
1132   return static_cast<enum Iostat>(io.EndIoStatement());
1133 }
1134 } // namespace Fortran::runtime::io
1135