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[]{
593       "SEQUENTIAL", "DIRECT", "STREAM", "APPEND", nullptr};
594   switch (IdentifyValue(keyword, length, keywords)) {
595   case 0:
596     open->set_access(Access::Sequential);
597     break;
598   case 1:
599     open->set_access(Access::Direct);
600     break;
601   case 2:
602     open->set_access(Access::Stream);
603     break;
604   case 3: // Sun Fortran extension ACCESS=APPEND: treat as if POSITION=APPEND
605     open->set_position(Position::Append);
606     break;
607   default:
608     open->SignalError(IostatErrorInKeyword, "Invalid ACCESS='%.*s'",
609         static_cast<int>(length), keyword);
610   }
611   return true;
612 }
613 
614 bool IONAME(SetAction)(Cookie cookie, const char *keyword, std::size_t length) {
615   IoStatementState &io{*cookie};
616   auto *open{io.get_if<OpenStatementState>()};
617   if (!open) {
618     io.GetIoErrorHandler().Crash(
619         "SetAction() called when not in an OPEN statement");
620   }
621   std::optional<Action> action;
622   static const char *keywords[]{"READ", "WRITE", "READWRITE", nullptr};
623   switch (IdentifyValue(keyword, length, keywords)) {
624   case 0:
625     action = Action::Read;
626     break;
627   case 1:
628     action = Action::Write;
629     break;
630   case 2:
631     action = Action::ReadWrite;
632     break;
633   default:
634     open->SignalError(IostatErrorInKeyword, "Invalid ACTION='%.*s'",
635         static_cast<int>(length), keyword);
636     return false;
637   }
638   RUNTIME_CHECK(io.GetIoErrorHandler(), action.has_value());
639   if (open->wasExtant()) {
640     if ((*action != Action::Write) != open->unit().mayRead() ||
641         (*action != Action::Read) != open->unit().mayWrite()) {
642       open->SignalError("ACTION= may not be changed on an open unit");
643     }
644   }
645   open->set_action(*action);
646   return true;
647 }
648 
649 bool IONAME(SetAsynchronous)(
650     Cookie cookie, const char *keyword, std::size_t length) {
651   IoStatementState &io{*cookie};
652   auto *open{io.get_if<OpenStatementState>()};
653   if (!open) {
654     io.GetIoErrorHandler().Crash(
655         "SetAsynchronous() called when not in an OPEN statement");
656   }
657   static const char *keywords[]{"YES", "NO", nullptr};
658   switch (IdentifyValue(keyword, length, keywords)) {
659   case 0:
660     open->unit().set_mayAsynchronous(true);
661     return true;
662   case 1:
663     open->unit().set_mayAsynchronous(false);
664     return true;
665   default:
666     open->SignalError(IostatErrorInKeyword, "Invalid ASYNCHRONOUS='%.*s'",
667         static_cast<int>(length), keyword);
668     return false;
669   }
670 }
671 
672 bool IONAME(SetCarriagecontrol)(
673     Cookie cookie, const char *keyword, std::size_t length) {
674   IoStatementState &io{*cookie};
675   auto *open{io.get_if<OpenStatementState>()};
676   if (!open) {
677     io.GetIoErrorHandler().Crash(
678         "SetCarriageControl() called when not in an OPEN statement");
679   }
680   static const char *keywords[]{"LIST", "FORTRAN", "NONE", nullptr};
681   switch (IdentifyValue(keyword, length, keywords)) {
682   case 0:
683     return true;
684   case 1:
685   case 2:
686     open->SignalError(IostatErrorInKeyword,
687         "Unimplemented CARRIAGECONTROL='%.*s'", static_cast<int>(length),
688         keyword);
689     return false;
690   default:
691     open->SignalError(IostatErrorInKeyword, "Invalid CARRIAGECONTROL='%.*s'",
692         static_cast<int>(length), keyword);
693     return false;
694   }
695 }
696 
697 bool IONAME(SetConvert)(
698     Cookie cookie, const char *keyword, std::size_t length) {
699   IoStatementState &io{*cookie};
700   auto *open{io.get_if<OpenStatementState>()};
701   if (!open) {
702     io.GetIoErrorHandler().Crash(
703         "SetConvert() called when not in an OPEN statement");
704   }
705   if (auto convert{GetConvertFromString(keyword, length)}) {
706     open->set_convert(*convert);
707     return true;
708   } else {
709     open->SignalError(IostatErrorInKeyword, "Invalid CONVERT='%.*s'",
710         static_cast<int>(length), keyword);
711     return false;
712   }
713 }
714 
715 bool IONAME(SetEncoding)(
716     Cookie cookie, const char *keyword, std::size_t length) {
717   IoStatementState &io{*cookie};
718   auto *open{io.get_if<OpenStatementState>()};
719   if (!open) {
720     io.GetIoErrorHandler().Crash(
721         "SetEncoding() called when not in an OPEN statement");
722   }
723   bool isUTF8{false};
724   static const char *keywords[]{"UTF-8", "DEFAULT", nullptr};
725   switch (IdentifyValue(keyword, length, keywords)) {
726   case 0:
727     isUTF8 = true;
728     break;
729   case 1:
730     isUTF8 = false;
731     break;
732   default:
733     open->SignalError(IostatErrorInKeyword, "Invalid ENCODING='%.*s'",
734         static_cast<int>(length), keyword);
735   }
736   if (isUTF8 != open->unit().isUTF8) {
737     if (open->wasExtant()) {
738       open->SignalError("ENCODING= may not be changed on an open unit");
739     }
740     open->unit().isUTF8 = isUTF8;
741   }
742   return true;
743 }
744 
745 bool IONAME(SetForm)(Cookie cookie, const char *keyword, std::size_t length) {
746   IoStatementState &io{*cookie};
747   auto *open{io.get_if<OpenStatementState>()};
748   if (!open) {
749     io.GetIoErrorHandler().Crash(
750         "SetForm() called when not in an OPEN statement");
751   }
752   static const char *keywords[]{"FORMATTED", "UNFORMATTED", nullptr};
753   switch (IdentifyValue(keyword, length, keywords)) {
754   case 0:
755     open->set_isUnformatted(false);
756     break;
757   case 1:
758     open->set_isUnformatted(true);
759     break;
760   default:
761     open->SignalError(IostatErrorInKeyword, "Invalid FORM='%.*s'",
762         static_cast<int>(length), keyword);
763   }
764   return true;
765 }
766 
767 bool IONAME(SetPosition)(
768     Cookie cookie, const char *keyword, std::size_t length) {
769   IoStatementState &io{*cookie};
770   auto *open{io.get_if<OpenStatementState>()};
771   if (!open) {
772     io.GetIoErrorHandler().Crash(
773         "SetPosition() called when not in an OPEN statement");
774   }
775   static const char *positions[]{"ASIS", "REWIND", "APPEND", nullptr};
776   switch (IdentifyValue(keyword, length, positions)) {
777   case 0:
778     open->set_position(Position::AsIs);
779     return true;
780   case 1:
781     open->set_position(Position::Rewind);
782     return true;
783   case 2:
784     open->set_position(Position::Append);
785     return true;
786   default:
787     io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
788         "Invalid POSITION='%.*s'", static_cast<int>(length), keyword);
789   }
790   return true;
791 }
792 
793 bool IONAME(SetRecl)(Cookie cookie, std::size_t n) {
794   IoStatementState &io{*cookie};
795   auto *open{io.get_if<OpenStatementState>()};
796   if (!open) {
797     io.GetIoErrorHandler().Crash(
798         "SetRecl() called when not in an OPEN statement");
799   }
800   if (n <= 0) {
801     io.GetIoErrorHandler().SignalError("RECL= must be greater than zero");
802   }
803   if (open->wasExtant() && open->unit().isFixedRecordLength &&
804       open->unit().recordLength.value_or(n) != static_cast<std::int64_t>(n)) {
805     open->SignalError("RECL= may not be changed for an open unit");
806   }
807   open->unit().isFixedRecordLength = true;
808   open->unit().recordLength = n;
809   return true;
810 }
811 
812 bool IONAME(SetStatus)(Cookie cookie, const char *keyword, std::size_t length) {
813   IoStatementState &io{*cookie};
814   if (auto *open{io.get_if<OpenStatementState>()}) {
815     static const char *statuses[]{
816         "OLD", "NEW", "SCRATCH", "REPLACE", "UNKNOWN", nullptr};
817     switch (IdentifyValue(keyword, length, statuses)) {
818     case 0:
819       open->set_status(OpenStatus::Old);
820       return true;
821     case 1:
822       open->set_status(OpenStatus::New);
823       return true;
824     case 2:
825       open->set_status(OpenStatus::Scratch);
826       return true;
827     case 3:
828       open->set_status(OpenStatus::Replace);
829       return true;
830     case 4:
831       open->set_status(OpenStatus::Unknown);
832       return true;
833     default:
834       io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
835           "Invalid STATUS='%.*s'", static_cast<int>(length), keyword);
836     }
837     return false;
838   }
839   if (auto *close{io.get_if<CloseStatementState>()}) {
840     static const char *statuses[]{"KEEP", "DELETE", nullptr};
841     switch (IdentifyValue(keyword, length, statuses)) {
842     case 0:
843       close->set_status(CloseStatus::Keep);
844       return true;
845     case 1:
846       close->set_status(CloseStatus::Delete);
847       return true;
848     default:
849       io.GetIoErrorHandler().SignalError(IostatErrorInKeyword,
850           "Invalid STATUS='%.*s'", static_cast<int>(length), keyword);
851     }
852     return false;
853   }
854   if (io.get_if<NoopCloseStatementState>()) {
855     return true; // don't bother validating STATUS= in a no-op CLOSE
856   }
857   io.GetIoErrorHandler().Crash(
858       "SetStatus() called when not in an OPEN or CLOSE statement");
859 }
860 
861 bool IONAME(SetFile)(Cookie cookie, const char *path, std::size_t chars) {
862   IoStatementState &io{*cookie};
863   if (auto *open{io.get_if<OpenStatementState>()}) {
864     open->set_path(path, chars);
865     return true;
866   }
867   io.GetIoErrorHandler().Crash(
868       "SetFile() called when not in an OPEN statement");
869   return false;
870 }
871 
872 bool IONAME(GetNewUnit)(Cookie cookie, int &unit, int kind) {
873   IoStatementState &io{*cookie};
874   auto *open{io.get_if<OpenStatementState>()};
875   if (!open) {
876     io.GetIoErrorHandler().Crash(
877         "GetNewUnit() called when not in an OPEN statement");
878   }
879   if (!SetInteger(unit, kind, open->unit().unitNumber())) {
880     open->SignalError("GetNewUnit(): Bad INTEGER kind(%d) for result");
881   }
882   return true;
883 }
884 
885 // Data transfers
886 
887 bool IONAME(OutputDescriptor)(Cookie cookie, const Descriptor &descriptor) {
888   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
889 }
890 
891 bool IONAME(InputDescriptor)(Cookie cookie, const Descriptor &descriptor) {
892   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
893 }
894 
895 bool IONAME(OutputUnformattedBlock)(Cookie cookie, const char *x,
896     std::size_t length, std::size_t elementBytes) {
897   IoStatementState &io{*cookie};
898   if (auto *unf{io.get_if<UnformattedIoStatementState<Direction::Output>>()}) {
899     return unf->Emit(x, length, elementBytes);
900   }
901   io.GetIoErrorHandler().Crash("OutputUnformattedBlock() called for an I/O "
902                                "statement that is not unformatted output");
903   return false;
904 }
905 
906 bool IONAME(InputUnformattedBlock)(
907     Cookie cookie, char *x, std::size_t length, std::size_t elementBytes) {
908   IoStatementState &io{*cookie};
909   io.BeginReadingRecord();
910   if (io.GetIoErrorHandler().InError()) {
911     return false;
912   }
913   if (auto *unf{io.get_if<UnformattedIoStatementState<Direction::Input>>()}) {
914     return unf->Receive(x, length, elementBytes);
915   }
916   io.GetIoErrorHandler().Crash("InputUnformattedBlock() called for an I/O "
917                                "statement that is not unformatted output");
918   return false;
919 }
920 
921 bool IONAME(OutputInteger64)(Cookie cookie, std::int64_t n) {
922   cookie->CheckFormattedStmtType<Direction::Output>("OutputInteger64");
923   StaticDescriptor staticDescriptor;
924   Descriptor &descriptor{staticDescriptor.descriptor()};
925   descriptor.Establish(
926       TypeCategory::Integer, sizeof n, reinterpret_cast<void *>(&n), 0);
927   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
928 }
929 
930 bool IONAME(InputInteger)(Cookie cookie, std::int64_t &n, int kind) {
931   cookie->CheckFormattedStmtType<Direction::Input>("InputInteger");
932   StaticDescriptor staticDescriptor;
933   Descriptor &descriptor{staticDescriptor.descriptor()};
934   descriptor.Establish(
935       TypeCategory::Integer, kind, reinterpret_cast<void *>(&n), 0);
936   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
937 }
938 
939 bool IONAME(OutputReal32)(Cookie cookie, float x) {
940   cookie->CheckFormattedStmtType<Direction::Output>("OutputReal32");
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   cookie->CheckFormattedStmtType<Direction::Output>("OutputReal64");
949   StaticDescriptor staticDescriptor;
950   Descriptor &descriptor{staticDescriptor.descriptor()};
951   descriptor.Establish(TypeCategory::Real, 8, reinterpret_cast<void *>(&x), 0);
952   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
953 }
954 
955 bool IONAME(InputReal32)(Cookie cookie, float &x) {
956   cookie->CheckFormattedStmtType<Direction::Input>("InputReal32");
957   StaticDescriptor staticDescriptor;
958   Descriptor &descriptor{staticDescriptor.descriptor()};
959   descriptor.Establish(TypeCategory::Real, 4, reinterpret_cast<void *>(&x), 0);
960   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
961 }
962 
963 bool IONAME(InputReal64)(Cookie cookie, double &x) {
964   cookie->CheckFormattedStmtType<Direction::Input>("InputReal64");
965   StaticDescriptor staticDescriptor;
966   Descriptor &descriptor{staticDescriptor.descriptor()};
967   descriptor.Establish(TypeCategory::Real, 8, reinterpret_cast<void *>(&x), 0);
968   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
969 }
970 
971 bool IONAME(OutputComplex32)(Cookie cookie, float r, float i) {
972   cookie->CheckFormattedStmtType<Direction::Output>("OutputComplex32");
973   float z[2]{r, i};
974   StaticDescriptor staticDescriptor;
975   Descriptor &descriptor{staticDescriptor.descriptor()};
976   descriptor.Establish(
977       TypeCategory::Complex, 4, reinterpret_cast<void *>(&z), 0);
978   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
979 }
980 
981 bool IONAME(OutputComplex64)(Cookie cookie, double r, double i) {
982   cookie->CheckFormattedStmtType<Direction::Output>("OutputComplex64");
983   double z[2]{r, i};
984   StaticDescriptor staticDescriptor;
985   Descriptor &descriptor{staticDescriptor.descriptor()};
986   descriptor.Establish(
987       TypeCategory::Complex, 8, reinterpret_cast<void *>(&z), 0);
988   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
989 }
990 
991 bool IONAME(InputComplex32)(Cookie cookie, float z[2]) {
992   cookie->CheckFormattedStmtType<Direction::Input>("InputComplex32");
993   StaticDescriptor staticDescriptor;
994   Descriptor &descriptor{staticDescriptor.descriptor()};
995   descriptor.Establish(
996       TypeCategory::Complex, 4, reinterpret_cast<void *>(z), 0);
997   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
998 }
999 
1000 bool IONAME(InputComplex64)(Cookie cookie, double z[2]) {
1001   cookie->CheckFormattedStmtType<Direction::Input>("InputComplex64");
1002   StaticDescriptor staticDescriptor;
1003   Descriptor &descriptor{staticDescriptor.descriptor()};
1004   descriptor.Establish(
1005       TypeCategory::Complex, 8, reinterpret_cast<void *>(z), 0);
1006   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1007 }
1008 
1009 bool IONAME(OutputCharacter)(
1010     Cookie cookie, const char *x, std::size_t length, int kind) {
1011   cookie->CheckFormattedStmtType<Direction::Output>("OutputCharacter");
1012   StaticDescriptor staticDescriptor;
1013   Descriptor &descriptor{staticDescriptor.descriptor()};
1014   descriptor.Establish(
1015       kind, length, reinterpret_cast<void *>(const_cast<char *>(x)), 0);
1016   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1017 }
1018 
1019 bool IONAME(OutputAscii)(Cookie cookie, const char *x, std::size_t length) {
1020   return IONAME(OutputCharacter(cookie, x, length, 1));
1021 }
1022 
1023 bool IONAME(InputCharacter)(
1024     Cookie cookie, char *x, std::size_t length, int kind) {
1025   cookie->CheckFormattedStmtType<Direction::Input>("InputCharacter");
1026   StaticDescriptor staticDescriptor;
1027   Descriptor &descriptor{staticDescriptor.descriptor()};
1028   descriptor.Establish(kind, length, reinterpret_cast<void *>(x), 0);
1029   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1030 }
1031 
1032 bool IONAME(InputAscii)(Cookie cookie, char *x, std::size_t length) {
1033   return IONAME(InputCharacter(cookie, x, length, 1));
1034 }
1035 
1036 bool IONAME(OutputLogical)(Cookie cookie, bool truth) {
1037   cookie->CheckFormattedStmtType<Direction::Output>("OutputLogical");
1038   StaticDescriptor staticDescriptor;
1039   Descriptor &descriptor{staticDescriptor.descriptor()};
1040   descriptor.Establish(
1041       TypeCategory::Logical, sizeof truth, reinterpret_cast<void *>(&truth), 0);
1042   return descr::DescriptorIO<Direction::Output>(*cookie, descriptor);
1043 }
1044 
1045 bool IONAME(InputLogical)(Cookie cookie, bool &truth) {
1046   cookie->CheckFormattedStmtType<Direction::Input>("InputLogical");
1047   StaticDescriptor staticDescriptor;
1048   Descriptor &descriptor{staticDescriptor.descriptor()};
1049   descriptor.Establish(
1050       TypeCategory::Logical, sizeof truth, reinterpret_cast<void *>(&truth), 0);
1051   return descr::DescriptorIO<Direction::Input>(*cookie, descriptor);
1052 }
1053 
1054 void IONAME(GetIoMsg)(Cookie cookie, char *msg, std::size_t length) {
1055   IoErrorHandler &handler{cookie->GetIoErrorHandler()};
1056   if (handler.InError()) { // leave "msg" alone when no error
1057     handler.GetIoMsg(msg, length);
1058   }
1059 }
1060 
1061 bool IONAME(InquireCharacter)(Cookie cookie, InquiryKeywordHash inquiry,
1062     char *result, std::size_t length) {
1063   IoStatementState &io{*cookie};
1064   return io.Inquire(inquiry, result, length);
1065 }
1066 
1067 bool IONAME(InquireLogical)(
1068     Cookie cookie, InquiryKeywordHash inquiry, bool &result) {
1069   IoStatementState &io{*cookie};
1070   return io.Inquire(inquiry, result);
1071 }
1072 
1073 bool IONAME(InquirePendingId)(Cookie cookie, std::int64_t id, bool &result) {
1074   IoStatementState &io{*cookie};
1075   return io.Inquire(HashInquiryKeyword("PENDING"), id, result);
1076 }
1077 
1078 bool IONAME(InquireInteger64)(
1079     Cookie cookie, InquiryKeywordHash inquiry, std::int64_t &result, int kind) {
1080   IoStatementState &io{*cookie};
1081   std::int64_t n;
1082   if (io.Inquire(inquiry, n)) {
1083     SetInteger(result, kind, n);
1084     return true;
1085   }
1086   return false;
1087 }
1088 
1089 enum Iostat IONAME(EndIoStatement)(Cookie cookie) {
1090   IoStatementState &io{*cookie};
1091   return static_cast<enum Iostat>(io.EndIoStatement());
1092 }
1093 } // namespace Fortran::runtime::io
1094