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