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