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