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