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