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