1 //===-- runtime/io-stmt.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 #include "io-stmt.h"
10 #include "connection.h"
11 #include "format.h"
12 #include "memory.h"
13 #include "tools.h"
14 #include "unit.h"
15 #include <algorithm>
16 #include <cstdio>
17 #include <cstring>
18 #include <limits>
19 
20 namespace Fortran::runtime::io {
21 
EndIoStatement()22 int IoStatementBase::EndIoStatement() { return GetIoStat(); }
23 
GetNextDataEdit(IoStatementState &,int)24 std::optional<DataEdit> IoStatementBase::GetNextDataEdit(
25     IoStatementState &, int) {
26   return std::nullopt;
27 }
28 
Inquire(InquiryKeywordHash,char *,std::size_t)29 bool IoStatementBase::Inquire(InquiryKeywordHash, char *, std::size_t) {
30   Crash(
31       "IoStatementBase::Inquire() called for I/O statement other than INQUIRE");
32   return false;
33 }
34 
Inquire(InquiryKeywordHash,bool &)35 bool IoStatementBase::Inquire(InquiryKeywordHash, bool &) {
36   Crash(
37       "IoStatementBase::Inquire() called for I/O statement other than INQUIRE");
38   return false;
39 }
40 
Inquire(InquiryKeywordHash,std::int64_t,bool &)41 bool IoStatementBase::Inquire(InquiryKeywordHash, std::int64_t, bool &) {
42   Crash(
43       "IoStatementBase::Inquire() called for I/O statement other than INQUIRE");
44   return false;
45 }
46 
Inquire(InquiryKeywordHash,std::int64_t &)47 bool IoStatementBase::Inquire(InquiryKeywordHash, std::int64_t &) {
48   Crash(
49       "IoStatementBase::Inquire() called for I/O statement other than INQUIRE");
50   return false;
51 }
52 
BadInquiryKeywordHashCrash(InquiryKeywordHash inquiry)53 void IoStatementBase::BadInquiryKeywordHashCrash(InquiryKeywordHash inquiry) {
54   char buffer[16];
55   const char *decode{InquiryKeywordHashDecode(buffer, sizeof buffer, inquiry)};
56   Crash("bad InquiryKeywordHash 0x%x (%s)", inquiry,
57       decode ? decode : "(cannot decode)");
58 }
59 
60 template <Direction DIR, typename CHAR>
InternalIoStatementState(Buffer scalar,std::size_t length,const char * sourceFile,int sourceLine)61 InternalIoStatementState<DIR, CHAR>::InternalIoStatementState(
62     Buffer scalar, std::size_t length, const char *sourceFile, int sourceLine)
63     : IoStatementBase{sourceFile, sourceLine}, unit_{scalar, length} {}
64 
65 template <Direction DIR, typename CHAR>
InternalIoStatementState(const Descriptor & d,const char * sourceFile,int sourceLine)66 InternalIoStatementState<DIR, CHAR>::InternalIoStatementState(
67     const Descriptor &d, const char *sourceFile, int sourceLine)
68     : IoStatementBase{sourceFile, sourceLine}, unit_{d, *this} {}
69 
70 template <Direction DIR, typename CHAR>
Emit(const CharType * data,std::size_t chars,std::size_t)71 bool InternalIoStatementState<DIR, CHAR>::Emit(
72     const CharType *data, std::size_t chars, std::size_t /*elementBytes*/) {
73   if constexpr (DIR == Direction::Input) {
74     Crash("InternalIoStatementState<Direction::Input>::Emit() called");
75     return false;
76   }
77   return unit_.Emit(data, chars, *this);
78 }
79 
80 template <Direction DIR, typename CHAR>
GetCurrentChar()81 std::optional<char32_t> InternalIoStatementState<DIR, CHAR>::GetCurrentChar() {
82   if constexpr (DIR == Direction::Output) {
83     Crash(
84         "InternalIoStatementState<Direction::Output>::GetCurrentChar() called");
85     return std::nullopt;
86   }
87   return unit_.GetCurrentChar(*this);
88 }
89 
90 template <Direction DIR, typename CHAR>
AdvanceRecord(int n)91 bool InternalIoStatementState<DIR, CHAR>::AdvanceRecord(int n) {
92   while (n-- > 0) {
93     if (!unit_.AdvanceRecord(*this)) {
94       return false;
95     }
96   }
97   return true;
98 }
99 
100 template <Direction DIR, typename CHAR>
BackspaceRecord()101 void InternalIoStatementState<DIR, CHAR>::BackspaceRecord() {
102   unit_.BackspaceRecord(*this);
103 }
104 
105 template <Direction DIR, typename CHAR>
EndIoStatement()106 int InternalIoStatementState<DIR, CHAR>::EndIoStatement() {
107   if constexpr (DIR == Direction::Output) {
108     unit_.EndIoStatement(); // fill
109   }
110   auto result{IoStatementBase::EndIoStatement()};
111   if (free_) {
112     FreeMemory(this);
113   }
114   return result;
115 }
116 
117 template <Direction DIR, typename CHAR>
HandleAbsolutePosition(std::int64_t n)118 void InternalIoStatementState<DIR, CHAR>::HandleAbsolutePosition(
119     std::int64_t n) {
120   return unit_.HandleAbsolutePosition(n);
121 }
122 
123 template <Direction DIR, typename CHAR>
HandleRelativePosition(std::int64_t n)124 void InternalIoStatementState<DIR, CHAR>::HandleRelativePosition(
125     std::int64_t n) {
126   return unit_.HandleRelativePosition(n);
127 }
128 
129 template <Direction DIR, typename CHAR>
InternalFormattedIoStatementState(Buffer buffer,std::size_t length,const CHAR * format,std::size_t formatLength,const char * sourceFile,int sourceLine)130 InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState(
131     Buffer buffer, std::size_t length, const CHAR *format,
132     std::size_t formatLength, const char *sourceFile, int sourceLine)
133     : InternalIoStatementState<DIR, CHAR>{buffer, length, sourceFile,
134           sourceLine},
135       ioStatementState_{*this}, format_{*this, format, formatLength} {}
136 
137 template <Direction DIR, typename CHAR>
InternalFormattedIoStatementState(const Descriptor & d,const CHAR * format,std::size_t formatLength,const char * sourceFile,int sourceLine)138 InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState(
139     const Descriptor &d, const CHAR *format, std::size_t formatLength,
140     const char *sourceFile, int sourceLine)
141     : InternalIoStatementState<DIR, CHAR>{d, sourceFile, sourceLine},
142       ioStatementState_{*this}, format_{*this, format, formatLength} {}
143 
144 template <Direction DIR, typename CHAR>
EndIoStatement()145 int InternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
146   if constexpr (DIR == Direction::Output) {
147     format_.Finish(*this); // ignore any remaining input positioning actions
148   }
149   return InternalIoStatementState<DIR, CHAR>::EndIoStatement();
150 }
151 
152 template <Direction DIR, typename CHAR>
InternalListIoStatementState(Buffer buffer,std::size_t length,const char * sourceFile,int sourceLine)153 InternalListIoStatementState<DIR, CHAR>::InternalListIoStatementState(
154     Buffer buffer, std::size_t length, const char *sourceFile, int sourceLine)
155     : InternalIoStatementState<DIR, CharType>{buffer, length, sourceFile,
156           sourceLine},
157       ioStatementState_{*this} {}
158 
159 template <Direction DIR, typename CHAR>
InternalListIoStatementState(const Descriptor & d,const char * sourceFile,int sourceLine)160 InternalListIoStatementState<DIR, CHAR>::InternalListIoStatementState(
161     const Descriptor &d, const char *sourceFile, int sourceLine)
162     : InternalIoStatementState<DIR, CharType>{d, sourceFile, sourceLine},
163       ioStatementState_{*this} {}
164 
ExternalIoStatementBase(ExternalFileUnit & unit,const char * sourceFile,int sourceLine)165 ExternalIoStatementBase::ExternalIoStatementBase(
166     ExternalFileUnit &unit, const char *sourceFile, int sourceLine)
167     : IoStatementBase{sourceFile, sourceLine}, unit_{unit} {}
168 
mutableModes()169 MutableModes &ExternalIoStatementBase::mutableModes() { return unit_.modes; }
170 
GetConnectionState()171 ConnectionState &ExternalIoStatementBase::GetConnectionState() { return unit_; }
172 
EndIoStatement()173 int ExternalIoStatementBase::EndIoStatement() {
174   if (unit_.nonAdvancing) {
175     unit_.leftTabLimit = unit_.furthestPositionInRecord;
176     unit_.nonAdvancing = false;
177   } else {
178     unit_.leftTabLimit.reset();
179   }
180   auto result{IoStatementBase::EndIoStatement()};
181   unit_.EndIoStatement(); // annihilates *this in unit_.u_
182   return result;
183 }
184 
set_path(const char * path,std::size_t length)185 void OpenStatementState::set_path(const char *path, std::size_t length) {
186   pathLength_ = TrimTrailingSpaces(path, length);
187   path_ = SaveDefaultCharacter(path, pathLength_, *this);
188 }
189 
EndIoStatement()190 int OpenStatementState::EndIoStatement() {
191   if (wasExtant_ && status_ && *status_ != OpenStatus::Old) {
192     SignalError("OPEN statement for connected unit may not have STATUS= other "
193                 "than 'OLD'");
194   }
195   if (path_.get() || wasExtant_ ||
196       (status_ && *status_ == OpenStatus::Scratch)) {
197     unit().OpenUnit(status_.value_or(OpenStatus::Unknown), action_, position_,
198         std::move(path_), pathLength_, convert_, *this);
199   } else {
200     unit().OpenAnonymousUnit(status_.value_or(OpenStatus::Unknown), action_,
201         position_, convert_, *this);
202   }
203   if (access_) {
204     if (*access_ != unit().access) {
205       if (wasExtant_) {
206         SignalError("ACCESS= may not be changed on an open unit");
207       }
208     }
209     unit().access = *access_;
210   }
211   if (!isUnformatted_) {
212     isUnformatted_ = unit().access != Access::Sequential;
213   }
214   if (*isUnformatted_ != unit().isUnformatted) {
215     if (wasExtant_) {
216       SignalError("FORM= may not be changed on an open unit");
217     }
218     unit().isUnformatted = *isUnformatted_;
219   }
220   return ExternalIoStatementBase::EndIoStatement();
221 }
222 
EndIoStatement()223 int CloseStatementState::EndIoStatement() {
224   int result{ExternalIoStatementBase::EndIoStatement()};
225   unit().CloseUnit(status_, *this);
226   unit().DestroyClosed();
227   return result;
228 }
229 
EndIoStatement()230 int NoUnitIoStatementState::EndIoStatement() {
231   auto result{IoStatementBase::EndIoStatement()};
232   FreeMemory(this);
233   return result;
234 }
235 
EndIoStatement()236 template <Direction DIR> int ExternalIoStatementState<DIR>::EndIoStatement() {
237   if constexpr (DIR == Direction::Input) {
238     BeginReadingRecord(); // in case of READ with no data items
239     if (!unit().nonAdvancing) {
240       FinishReadingRecord();
241     }
242   } else {
243     if (!unit().nonAdvancing) {
244       unit().AdvanceRecord(*this);
245     }
246     unit().FlushIfTerminal(*this);
247   }
248   return ExternalIoStatementBase::EndIoStatement();
249 }
250 
251 template <Direction DIR>
Emit(const char * data,std::size_t bytes,std::size_t elementBytes)252 bool ExternalIoStatementState<DIR>::Emit(
253     const char *data, std::size_t bytes, std::size_t elementBytes) {
254   if constexpr (DIR == Direction::Input) {
255     Crash("ExternalIoStatementState::Emit(char) called for input statement");
256   }
257   return unit().Emit(data, bytes, elementBytes, *this);
258 }
259 
260 template <Direction DIR>
Emit(const char16_t * data,std::size_t chars)261 bool ExternalIoStatementState<DIR>::Emit(
262     const char16_t *data, std::size_t chars) {
263   if constexpr (DIR == Direction::Input) {
264     Crash(
265         "ExternalIoStatementState::Emit(char16_t) called for input statement");
266   }
267   // TODO: UTF-8 encoding
268   return unit().Emit(reinterpret_cast<const char *>(data), chars * sizeof *data,
269       static_cast<int>(sizeof *data), *this);
270 }
271 
272 template <Direction DIR>
Emit(const char32_t * data,std::size_t chars)273 bool ExternalIoStatementState<DIR>::Emit(
274     const char32_t *data, std::size_t chars) {
275   if constexpr (DIR == Direction::Input) {
276     Crash(
277         "ExternalIoStatementState::Emit(char32_t) called for input statement");
278   }
279   // TODO: UTF-8 encoding
280   return unit().Emit(reinterpret_cast<const char *>(data), chars * sizeof *data,
281       static_cast<int>(sizeof *data), *this);
282 }
283 
284 template <Direction DIR>
GetCurrentChar()285 std::optional<char32_t> ExternalIoStatementState<DIR>::GetCurrentChar() {
286   if constexpr (DIR == Direction::Output) {
287     Crash(
288         "ExternalIoStatementState<Direction::Output>::GetCurrentChar() called");
289   }
290   return unit().GetCurrentChar(*this);
291 }
292 
293 template <Direction DIR>
AdvanceRecord(int n)294 bool ExternalIoStatementState<DIR>::AdvanceRecord(int n) {
295   while (n-- > 0) {
296     if (!unit().AdvanceRecord(*this)) {
297       return false;
298     }
299   }
300   return true;
301 }
302 
BackspaceRecord()303 template <Direction DIR> void ExternalIoStatementState<DIR>::BackspaceRecord() {
304   unit().BackspaceRecord(*this);
305 }
306 
307 template <Direction DIR>
HandleAbsolutePosition(std::int64_t n)308 void ExternalIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) {
309   return unit().HandleAbsolutePosition(n);
310 }
311 
312 template <Direction DIR>
HandleRelativePosition(std::int64_t n)313 void ExternalIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) {
314   return unit().HandleRelativePosition(n);
315 }
316 
317 template <Direction DIR>
BeginReadingRecord()318 void ExternalIoStatementState<DIR>::BeginReadingRecord() {
319   if constexpr (DIR == Direction::Input) {
320     unit().BeginReadingRecord(*this);
321   } else {
322     Crash("ExternalIoStatementState<Direction::Output>::BeginReadingRecord() "
323           "called");
324   }
325 }
326 
327 template <Direction DIR>
FinishReadingRecord()328 void ExternalIoStatementState<DIR>::FinishReadingRecord() {
329   if constexpr (DIR == Direction::Input) {
330     unit().FinishReadingRecord(*this);
331   } else {
332     Crash("ExternalIoStatementState<Direction::Output>::FinishReadingRecord() "
333           "called");
334   }
335 }
336 
337 template <Direction DIR, typename CHAR>
ExternalFormattedIoStatementState(ExternalFileUnit & unit,const CHAR * format,std::size_t formatLength,const char * sourceFile,int sourceLine)338 ExternalFormattedIoStatementState<DIR, CHAR>::ExternalFormattedIoStatementState(
339     ExternalFileUnit &unit, const CHAR *format, std::size_t formatLength,
340     const char *sourceFile, int sourceLine)
341     : ExternalIoStatementState<DIR>{unit, sourceFile, sourceLine},
342       mutableModes_{unit.modes}, format_{*this, format, formatLength} {}
343 
344 template <Direction DIR, typename CHAR>
EndIoStatement()345 int ExternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
346   format_.Finish(*this);
347   return ExternalIoStatementState<DIR>::EndIoStatement();
348 }
349 
GetNextDataEdit(int n)350 std::optional<DataEdit> IoStatementState::GetNextDataEdit(int n) {
351   return std::visit(
352       [&](auto &x) { return x.get().GetNextDataEdit(*this, n); }, u_);
353 }
354 
Emit(const char * data,std::size_t n,std::size_t elementBytes)355 bool IoStatementState::Emit(
356     const char *data, std::size_t n, std::size_t elementBytes) {
357   return std::visit(
358       [=](auto &x) { return x.get().Emit(data, n, elementBytes); }, u_);
359 }
360 
GetCurrentChar()361 std::optional<char32_t> IoStatementState::GetCurrentChar() {
362   return std::visit([&](auto &x) { return x.get().GetCurrentChar(); }, u_);
363 }
364 
AdvanceRecord(int n)365 bool IoStatementState::AdvanceRecord(int n) {
366   return std::visit([=](auto &x) { return x.get().AdvanceRecord(n); }, u_);
367 }
368 
BackspaceRecord()369 void IoStatementState::BackspaceRecord() {
370   std::visit([](auto &x) { x.get().BackspaceRecord(); }, u_);
371 }
372 
HandleRelativePosition(std::int64_t n)373 void IoStatementState::HandleRelativePosition(std::int64_t n) {
374   std::visit([=](auto &x) { x.get().HandleRelativePosition(n); }, u_);
375 }
376 
EndIoStatement()377 int IoStatementState::EndIoStatement() {
378   return std::visit([](auto &x) { return x.get().EndIoStatement(); }, u_);
379 }
380 
GetConnectionState()381 ConnectionState &IoStatementState::GetConnectionState() {
382   return std::visit(
383       [](auto &x) -> ConnectionState & { return x.get().GetConnectionState(); },
384       u_);
385 }
386 
mutableModes()387 MutableModes &IoStatementState::mutableModes() {
388   return std::visit(
389       [](auto &x) -> MutableModes & { return x.get().mutableModes(); }, u_);
390 }
391 
BeginReadingRecord()392 void IoStatementState::BeginReadingRecord() {
393   std::visit([](auto &x) { return x.get().BeginReadingRecord(); }, u_);
394 }
395 
GetIoErrorHandler() const396 IoErrorHandler &IoStatementState::GetIoErrorHandler() const {
397   return std::visit(
398       [](auto &x) -> IoErrorHandler & {
399         return static_cast<IoErrorHandler &>(x.get());
400       },
401       u_);
402 }
403 
GetExternalFileUnit() const404 ExternalFileUnit *IoStatementState::GetExternalFileUnit() const {
405   return std::visit([](auto &x) { return x.get().GetExternalFileUnit(); }, u_);
406 }
407 
EmitRepeated(char ch,std::size_t n)408 bool IoStatementState::EmitRepeated(char ch, std::size_t n) {
409   return std::visit(
410       [=](auto &x) {
411         for (std::size_t j{0}; j < n; ++j) {
412           if (!x.get().Emit(&ch, 1)) {
413             return false;
414           }
415         }
416         return true;
417       },
418       u_);
419 }
420 
EmitField(const char * p,std::size_t length,std::size_t width)421 bool IoStatementState::EmitField(
422     const char *p, std::size_t length, std::size_t width) {
423   if (width <= 0) {
424     width = static_cast<int>(length);
425   }
426   if (length > static_cast<std::size_t>(width)) {
427     return EmitRepeated('*', width);
428   } else {
429     return EmitRepeated(' ', static_cast<int>(width - length)) &&
430         Emit(p, length);
431   }
432 }
433 
SkipSpaces(std::optional<int> & remaining)434 std::optional<char32_t> IoStatementState::SkipSpaces(
435     std::optional<int> &remaining) {
436   while (!remaining || *remaining > 0) {
437     if (auto ch{GetCurrentChar()}) {
438       if (*ch != ' ' && *ch != '\t') {
439         return ch;
440       }
441       HandleRelativePosition(1);
442       if (remaining) {
443         --*remaining;
444       }
445     } else {
446       break;
447     }
448   }
449   return std::nullopt;
450 }
451 
NextInField(std::optional<int> & remaining)452 std::optional<char32_t> IoStatementState::NextInField(
453     std::optional<int> &remaining) {
454   if (!remaining) { // list-directed or namelist: check for separators
455     if (auto next{GetCurrentChar()}) {
456       switch (*next) {
457       case ' ':
458       case '\t':
459       case ',':
460       case ';':
461       case '/':
462       case '(':
463       case ')':
464       case '\'':
465       case '"':
466       case '*':
467       case '\n': // for stream access
468         break;
469       default:
470         HandleRelativePosition(1);
471         return next;
472       }
473     }
474   } else if (*remaining > 0) {
475     if (auto next{GetCurrentChar()}) {
476       --*remaining;
477       HandleRelativePosition(1);
478       return next;
479     }
480     const ConnectionState &connection{GetConnectionState()};
481     if (!connection.IsAtEOF() && connection.isFixedRecordLength &&
482         connection.recordLength &&
483         connection.positionInRecord >= *connection.recordLength) {
484       if (connection.modes.pad) { // PAD='YES'
485         --*remaining;
486         return std::optional<char32_t>{' '};
487       }
488       IoErrorHandler &handler{GetIoErrorHandler()};
489       if (connection.nonAdvancing) {
490         handler.SignalEor();
491       } else {
492         handler.SignalError(IostatRecordReadOverrun);
493       }
494     }
495   }
496   return std::nullopt;
497 }
498 
GetNextNonBlank()499 std::optional<char32_t> IoStatementState::GetNextNonBlank() {
500   auto ch{GetCurrentChar()};
501   while (!ch || *ch == ' ' || *ch == '\t') {
502     if (ch) {
503       HandleRelativePosition(1);
504     } else if (!AdvanceRecord()) {
505       return std::nullopt;
506     }
507     ch = GetCurrentChar();
508   }
509   return ch;
510 }
511 
NeedAdvance(const ConnectionState & connection,std::size_t width) const512 bool ListDirectedStatementState<Direction::Output>::NeedAdvance(
513     const ConnectionState &connection, std::size_t width) const {
514   return connection.positionInRecord > 0 &&
515       width > connection.RemainingSpaceInRecord();
516 }
517 
Inquire(InquiryKeywordHash inquiry,char * out,std::size_t chars)518 bool IoStatementState::Inquire(
519     InquiryKeywordHash inquiry, char *out, std::size_t chars) {
520   return std::visit(
521       [&](auto &x) { return x.get().Inquire(inquiry, out, chars); }, u_);
522 }
523 
Inquire(InquiryKeywordHash inquiry,bool & out)524 bool IoStatementState::Inquire(InquiryKeywordHash inquiry, bool &out) {
525   return std::visit([&](auto &x) { return x.get().Inquire(inquiry, out); }, u_);
526 }
527 
Inquire(InquiryKeywordHash inquiry,std::int64_t id,bool & out)528 bool IoStatementState::Inquire(
529     InquiryKeywordHash inquiry, std::int64_t id, bool &out) {
530   return std::visit(
531       [&](auto &x) { return x.get().Inquire(inquiry, id, out); }, u_);
532 }
533 
Inquire(InquiryKeywordHash inquiry,std::int64_t & n)534 bool IoStatementState::Inquire(InquiryKeywordHash inquiry, std::int64_t &n) {
535   return std::visit([&](auto &x) { return x.get().Inquire(inquiry, n); }, u_);
536 }
537 
EmitLeadingSpaceOrAdvance(IoStatementState & io,std::size_t length,bool isCharacter)538 bool ListDirectedStatementState<Direction::Output>::EmitLeadingSpaceOrAdvance(
539     IoStatementState &io, std::size_t length, bool isCharacter) {
540   if (length == 0) {
541     return true;
542   }
543   const ConnectionState &connection{io.GetConnectionState()};
544   int space{connection.positionInRecord == 0 ||
545       !(isCharacter && lastWasUndelimitedCharacter)};
546   lastWasUndelimitedCharacter = false;
547   if (NeedAdvance(connection, space + length)) {
548     return io.AdvanceRecord();
549   }
550   if (space) {
551     return io.Emit(" ", 1);
552   }
553   return true;
554 }
555 
556 std::optional<DataEdit>
GetNextDataEdit(IoStatementState & io,int maxRepeat)557 ListDirectedStatementState<Direction::Output>::GetNextDataEdit(
558     IoStatementState &io, int maxRepeat) {
559   DataEdit edit;
560   edit.descriptor = DataEdit::ListDirected;
561   edit.repeat = maxRepeat;
562   edit.modes = io.mutableModes();
563   return edit;
564 }
565 
566 std::optional<DataEdit>
GetNextDataEdit(IoStatementState & io,int maxRepeat)567 ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
568     IoStatementState &io, int maxRepeat) {
569   // N.B. list-directed transfers cannot be nonadvancing (C1221)
570   ConnectionState &connection{io.GetConnectionState()};
571   DataEdit edit;
572   edit.descriptor = DataEdit::ListDirected;
573   edit.repeat = 1; // may be overridden below
574   edit.modes = connection.modes;
575   if (hitSlash_) { // everything after '/' is nullified
576     edit.descriptor = DataEdit::ListDirectedNullValue;
577     return edit;
578   }
579   char32_t comma{','};
580   if (io.mutableModes().editingFlags & decimalComma) {
581     comma = ';';
582   }
583   if (remaining_ > 0 && !realPart_) { // "r*c" repetition in progress
584     while (connection.currentRecordNumber > initialRecordNumber_) {
585       io.BackspaceRecord();
586     }
587     connection.HandleAbsolutePosition(initialPositionInRecord_);
588     if (!imaginaryPart_) {
589       edit.repeat = std::min<int>(remaining_, maxRepeat);
590       auto ch{io.GetNextNonBlank()};
591       if (!ch || *ch == ' ' || *ch == '\t' || *ch == comma) {
592         // "r*" repeated null
593         edit.descriptor = DataEdit::ListDirectedNullValue;
594       }
595     }
596     remaining_ -= edit.repeat;
597     return edit;
598   }
599   // Skip separators, handle a "r*c" repeat count; see 13.10.2 in Fortran 2018
600   auto ch{io.GetNextNonBlank()};
601   if (imaginaryPart_) {
602     imaginaryPart_ = false;
603     if (ch && *ch == ')') {
604       io.HandleRelativePosition(1);
605       ch = io.GetNextNonBlank();
606     }
607   } else if (realPart_) {
608     realPart_ = false;
609     imaginaryPart_ = true;
610     edit.descriptor = DataEdit::ListDirectedImaginaryPart;
611   }
612   if (!ch) {
613     return std::nullopt;
614   }
615   if (*ch == '/') {
616     hitSlash_ = true;
617     edit.descriptor = DataEdit::ListDirectedNullValue;
618     return edit;
619   }
620   bool isFirstItem{isFirstItem_};
621   isFirstItem_ = false;
622   if (*ch == comma) {
623     if (isFirstItem) {
624       edit.descriptor = DataEdit::ListDirectedNullValue;
625       return edit;
626     }
627     // Consume comma & whitespace after previous item.
628     io.HandleRelativePosition(1);
629     ch = io.GetNextNonBlank();
630     if (!ch) {
631       return std::nullopt;
632     }
633     if (*ch == comma || *ch == '/') {
634       edit.descriptor = DataEdit::ListDirectedNullValue;
635       return edit;
636     }
637   }
638   if (imaginaryPart_) { // can't repeat components
639     return edit;
640   }
641   if (*ch >= '0' && *ch <= '9') { // look for "r*" repetition count
642     auto start{connection.positionInRecord};
643     int r{0};
644     do {
645       static auto constexpr clamp{(std::numeric_limits<int>::max() - '9') / 10};
646       if (r >= clamp) {
647         r = 0;
648         break;
649       }
650       r = 10 * r + (*ch - '0');
651       io.HandleRelativePosition(1);
652       ch = io.GetCurrentChar();
653     } while (ch && *ch >= '0' && *ch <= '9');
654     if (r > 0 && ch && *ch == '*') { // subtle: r must be nonzero
655       io.HandleRelativePosition(1);
656       ch = io.GetCurrentChar();
657       if (ch && *ch == '/') { // r*/
658         hitSlash_ = true;
659         edit.descriptor = DataEdit::ListDirectedNullValue;
660         return edit;
661       }
662       if (!ch || *ch == ' ' || *ch == '\t' || *ch == comma) { // "r*" null
663         edit.descriptor = DataEdit::ListDirectedNullValue;
664       }
665       edit.repeat = std::min<int>(r, maxRepeat);
666       remaining_ = r - edit.repeat;
667       initialRecordNumber_ = connection.currentRecordNumber;
668       initialPositionInRecord_ = connection.positionInRecord;
669     } else { // not a repetition count, just an integer value; rewind
670       connection.positionInRecord = start;
671     }
672   }
673   if (!imaginaryPart_ && ch && *ch == '(') {
674     realPart_ = true;
675     io.HandleRelativePosition(1);
676     edit.descriptor = DataEdit::ListDirectedRealPart;
677   }
678   return edit;
679 }
680 
681 template <Direction DIR>
Receive(char * data,std::size_t bytes,std::size_t elementBytes)682 bool UnformattedIoStatementState<DIR>::Receive(
683     char *data, std::size_t bytes, std::size_t elementBytes) {
684   if constexpr (DIR == Direction::Output) {
685     this->Crash(
686         "UnformattedIoStatementState::Receive() called for output statement");
687   }
688   return this->unit().Receive(data, bytes, elementBytes, *this);
689 }
690 
691 template <Direction DIR>
Emit(const char * data,std::size_t bytes,std::size_t elementBytes)692 bool UnformattedIoStatementState<DIR>::Emit(
693     const char *data, std::size_t bytes, std::size_t elementBytes) {
694   if constexpr (DIR == Direction::Input) {
695     this->Crash(
696         "UnformattedIoStatementState::Emit() called for input statement");
697   }
698   return ExternalIoStatementState<DIR>::Emit(data, bytes, elementBytes);
699 }
700 
701 template class InternalIoStatementState<Direction::Output>;
702 template class InternalIoStatementState<Direction::Input>;
703 template class InternalFormattedIoStatementState<Direction::Output>;
704 template class InternalFormattedIoStatementState<Direction::Input>;
705 template class InternalListIoStatementState<Direction::Output>;
706 template class InternalListIoStatementState<Direction::Input>;
707 template class ExternalIoStatementState<Direction::Output>;
708 template class ExternalIoStatementState<Direction::Input>;
709 template class ExternalFormattedIoStatementState<Direction::Output>;
710 template class ExternalFormattedIoStatementState<Direction::Input>;
711 template class ExternalListIoStatementState<Direction::Output>;
712 template class ExternalListIoStatementState<Direction::Input>;
713 template class UnformattedIoStatementState<Direction::Output>;
714 template class UnformattedIoStatementState<Direction::Input>;
715 
EndIoStatement()716 int ExternalMiscIoStatementState::EndIoStatement() {
717   ExternalFileUnit &ext{unit()};
718   switch (which_) {
719   case Flush:
720     ext.Flush(*this);
721     std::fflush(nullptr); // flushes C stdio output streams (12.9(2))
722     break;
723   case Backspace:
724     ext.BackspaceRecord(*this);
725     break;
726   case Endfile:
727     ext.Endfile(*this);
728     break;
729   case Rewind:
730     ext.Rewind(*this);
731     break;
732   }
733   return ExternalIoStatementBase::EndIoStatement();
734 }
735 
InquireUnitState(ExternalFileUnit & unit,const char * sourceFile,int sourceLine)736 InquireUnitState::InquireUnitState(
737     ExternalFileUnit &unit, const char *sourceFile, int sourceLine)
738     : ExternalIoStatementBase{unit, sourceFile, sourceLine} {}
739 
Inquire(InquiryKeywordHash inquiry,char * result,std::size_t length)740 bool InquireUnitState::Inquire(
741     InquiryKeywordHash inquiry, char *result, std::size_t length) {
742   const char *str{nullptr};
743   switch (inquiry) {
744   case HashInquiryKeyword("ACCESS"):
745     switch (unit().access) {
746     case Access::Sequential:
747       str = "SEQUENTIAL";
748       break;
749     case Access::Direct:
750       str = "DIRECT";
751       break;
752     case Access::Stream:
753       str = "STREAM";
754       break;
755     }
756     break;
757   case HashInquiryKeyword("ACTION"):
758     str = unit().mayWrite() ? unit().mayRead() ? "READWRITE" : "WRITE" : "READ";
759     break;
760   case HashInquiryKeyword("ASYNCHRONOUS"):
761     str = unit().mayAsynchronous() ? "YES" : "NO";
762     break;
763   case HashInquiryKeyword("BLANK"):
764     str = unit().isUnformatted                  ? "UNDEFINED"
765         : unit().modes.editingFlags & blankZero ? "ZERO"
766                                                 : "NULL";
767     break;
768   case HashInquiryKeyword("CARRIAGECONTROL"):
769     str = "LIST";
770     break;
771   case HashInquiryKeyword("CONVERT"):
772     str = unit().swapEndianness() ? "SWAP" : "NATIVE";
773     break;
774   case HashInquiryKeyword("DECIMAL"):
775     str = unit().isUnformatted                     ? "UNDEFINED"
776         : unit().modes.editingFlags & decimalComma ? "COMMA"
777                                                    : "POINT";
778     break;
779   case HashInquiryKeyword("DELIM"):
780     if (unit().isUnformatted) {
781       str = "UNDEFINED";
782     } else {
783       switch (unit().modes.delim) {
784       case '\'':
785         str = "APOSTROPHE";
786         break;
787       case '"':
788         str = "QUOTE";
789         break;
790       default:
791         str = "NONE";
792         break;
793       }
794     }
795     break;
796   case HashInquiryKeyword("DIRECT"):
797     str = unit().access == Access::Direct ||
798             (unit().mayPosition() && unit().isFixedRecordLength)
799         ? "YES"
800         : "NO";
801     break;
802   case HashInquiryKeyword("ENCODING"):
803     str = unit().isUnformatted ? "UNDEFINED"
804         : unit().isUTF8        ? "UTF-8"
805                                : "ASCII";
806     break;
807   case HashInquiryKeyword("FORM"):
808     str = unit().isUnformatted ? "UNFORMATTED" : "FORMATTED";
809     break;
810   case HashInquiryKeyword("FORMATTED"):
811     str = !unit().isUnformatted ? "YES" : "NO";
812     break;
813   case HashInquiryKeyword("NAME"):
814     str = unit().path();
815     if (!str) {
816       return true; // result is undefined
817     }
818     break;
819   case HashInquiryKeyword("PAD"):
820     str = unit().isUnformatted ? "UNDEFINED" : unit().modes.pad ? "YES" : "NO";
821     break;
822   case HashInquiryKeyword("POSITION"):
823     if (unit().access == Access::Direct) {
824       str = "UNDEFINED";
825     } else {
826       auto size{unit().knownSize()};
827       auto pos{unit().position()};
828       if (pos == size.value_or(pos + 1)) {
829         str = "APPEND";
830       } else if (pos == 0) {
831         str = "REWIND";
832       } else {
833         str = "ASIS"; // processor-dependent & no common behavior
834       }
835     }
836     break;
837   case HashInquiryKeyword("READ"):
838     str = unit().mayRead() ? "YES" : "NO";
839     break;
840   case HashInquiryKeyword("READWRITE"):
841     str = unit().mayRead() && unit().mayWrite() ? "YES" : "NO";
842     break;
843   case HashInquiryKeyword("ROUND"):
844     if (unit().isUnformatted) {
845       str = "UNDEFINED";
846     } else {
847       switch (unit().modes.round) {
848       case decimal::FortranRounding::RoundNearest:
849         str = "NEAREST";
850         break;
851       case decimal::FortranRounding::RoundUp:
852         str = "UP";
853         break;
854       case decimal::FortranRounding::RoundDown:
855         str = "DOWN";
856         break;
857       case decimal::FortranRounding::RoundToZero:
858         str = "ZERO";
859         break;
860       case decimal::FortranRounding::RoundCompatible:
861         str = "COMPATIBLE";
862         break;
863       }
864     }
865     break;
866   case HashInquiryKeyword("SEQUENTIAL"):
867     // "NO" for Direct, since Sequential would not work if
868     // the unit were reopened without RECL=.
869     str = unit().access == Access::Sequential ? "YES" : "NO";
870     break;
871   case HashInquiryKeyword("SIGN"):
872     str = unit().isUnformatted                 ? "UNDEFINED"
873         : unit().modes.editingFlags & signPlus ? "PLUS"
874                                                : "SUPPRESS";
875     break;
876   case HashInquiryKeyword("STREAM"):
877     str = unit().access == Access::Stream ? "YES" : "NO";
878     break;
879   case HashInquiryKeyword("WRITE"):
880     str = unit().mayWrite() ? "YES" : "NO";
881     break;
882   case HashInquiryKeyword("UNFORMATTED"):
883     str = unit().isUnformatted ? "YES" : "NO";
884     break;
885   }
886   if (str) {
887     ToFortranDefaultCharacter(result, length, str);
888     return true;
889   } else {
890     BadInquiryKeywordHashCrash(inquiry);
891     return false;
892   }
893 }
894 
Inquire(InquiryKeywordHash inquiry,bool & result)895 bool InquireUnitState::Inquire(InquiryKeywordHash inquiry, bool &result) {
896   switch (inquiry) {
897   case HashInquiryKeyword("EXIST"):
898     result = true;
899     return true;
900   case HashInquiryKeyword("NAMED"):
901     result = unit().path() != nullptr;
902     return true;
903   case HashInquiryKeyword("OPENED"):
904     result = true;
905     return true;
906   case HashInquiryKeyword("PENDING"):
907     result = false; // asynchronous I/O is not implemented
908     return true;
909   default:
910     BadInquiryKeywordHashCrash(inquiry);
911     return false;
912   }
913 }
914 
Inquire(InquiryKeywordHash inquiry,std::int64_t,bool & result)915 bool InquireUnitState::Inquire(
916     InquiryKeywordHash inquiry, std::int64_t, bool &result) {
917   switch (inquiry) {
918   case HashInquiryKeyword("PENDING"):
919     result = false; // asynchronous I/O is not implemented
920     return true;
921   default:
922     BadInquiryKeywordHashCrash(inquiry);
923     return false;
924   }
925 }
926 
Inquire(InquiryKeywordHash inquiry,std::int64_t & result)927 bool InquireUnitState::Inquire(
928     InquiryKeywordHash inquiry, std::int64_t &result) {
929   switch (inquiry) {
930   case HashInquiryKeyword("NEXTREC"):
931     if (unit().access == Access::Direct) {
932       result = unit().currentRecordNumber;
933     }
934     return true;
935   case HashInquiryKeyword("NUMBER"):
936     result = unit().unitNumber();
937     return true;
938   case HashInquiryKeyword("POS"):
939     result = unit().position();
940     return true;
941   case HashInquiryKeyword("RECL"):
942     if (unit().access == Access::Stream) {
943       result = -2;
944     } else if (unit().isFixedRecordLength && unit().recordLength) {
945       result = *unit().recordLength;
946     } else {
947       result = std::numeric_limits<std::uint32_t>::max();
948     }
949     return true;
950   case HashInquiryKeyword("SIZE"):
951     if (auto size{unit().knownSize()}) {
952       result = *size;
953     } else {
954       result = -1;
955     }
956     return true;
957   default:
958     BadInquiryKeywordHashCrash(inquiry);
959     return false;
960   }
961 }
962 
InquireNoUnitState(const char * sourceFile,int sourceLine)963 InquireNoUnitState::InquireNoUnitState(const char *sourceFile, int sourceLine)
964     : NoUnitIoStatementState{sourceFile, sourceLine, *this} {}
965 
Inquire(InquiryKeywordHash inquiry,char * result,std::size_t length)966 bool InquireNoUnitState::Inquire(
967     InquiryKeywordHash inquiry, char *result, std::size_t length) {
968   switch (inquiry) {
969   case HashInquiryKeyword("ACCESS"):
970   case HashInquiryKeyword("ACTION"):
971   case HashInquiryKeyword("ASYNCHRONOUS"):
972   case HashInquiryKeyword("BLANK"):
973   case HashInquiryKeyword("CARRIAGECONTROL"):
974   case HashInquiryKeyword("CONVERT"):
975   case HashInquiryKeyword("DECIMAL"):
976   case HashInquiryKeyword("DELIM"):
977   case HashInquiryKeyword("FORM"):
978   case HashInquiryKeyword("NAME"):
979   case HashInquiryKeyword("PAD"):
980   case HashInquiryKeyword("POSITION"):
981   case HashInquiryKeyword("ROUND"):
982   case HashInquiryKeyword("SIGN"):
983     ToFortranDefaultCharacter(result, length, "UNDEFINED");
984     return true;
985   case HashInquiryKeyword("DIRECT"):
986   case HashInquiryKeyword("ENCODING"):
987   case HashInquiryKeyword("FORMATTED"):
988   case HashInquiryKeyword("READ"):
989   case HashInquiryKeyword("READWRITE"):
990   case HashInquiryKeyword("SEQUENTIAL"):
991   case HashInquiryKeyword("STREAM"):
992   case HashInquiryKeyword("WRITE"):
993   case HashInquiryKeyword("UNFORMATTED"):
994     ToFortranDefaultCharacter(result, length, "UNKNONN");
995     return true;
996   default:
997     BadInquiryKeywordHashCrash(inquiry);
998     return false;
999   }
1000 }
1001 
Inquire(InquiryKeywordHash inquiry,bool & result)1002 bool InquireNoUnitState::Inquire(InquiryKeywordHash inquiry, bool &result) {
1003   switch (inquiry) {
1004   case HashInquiryKeyword("EXIST"):
1005     result = true;
1006     return true;
1007   case HashInquiryKeyword("NAMED"):
1008   case HashInquiryKeyword("OPENED"):
1009   case HashInquiryKeyword("PENDING"):
1010     result = false;
1011     return true;
1012   default:
1013     BadInquiryKeywordHashCrash(inquiry);
1014     return false;
1015   }
1016 }
1017 
Inquire(InquiryKeywordHash inquiry,std::int64_t,bool & result)1018 bool InquireNoUnitState::Inquire(
1019     InquiryKeywordHash inquiry, std::int64_t, bool &result) {
1020   switch (inquiry) {
1021   case HashInquiryKeyword("PENDING"):
1022     result = false;
1023     return true;
1024   default:
1025     BadInquiryKeywordHashCrash(inquiry);
1026     return false;
1027   }
1028 }
1029 
Inquire(InquiryKeywordHash inquiry,std::int64_t & result)1030 bool InquireNoUnitState::Inquire(
1031     InquiryKeywordHash inquiry, std::int64_t &result) {
1032   switch (inquiry) {
1033   case HashInquiryKeyword("NEXTREC"):
1034   case HashInquiryKeyword("NUMBER"):
1035   case HashInquiryKeyword("POS"):
1036   case HashInquiryKeyword("RECL"):
1037   case HashInquiryKeyword("SIZE"):
1038     result = -1;
1039     return true;
1040   default:
1041     BadInquiryKeywordHashCrash(inquiry);
1042     return false;
1043   }
1044 }
1045 
InquireUnconnectedFileState(OwningPtr<char> && path,const char * sourceFile,int sourceLine)1046 InquireUnconnectedFileState::InquireUnconnectedFileState(
1047     OwningPtr<char> &&path, const char *sourceFile, int sourceLine)
1048     : NoUnitIoStatementState{sourceFile, sourceLine, *this}, path_{std::move(
1049                                                                  path)} {}
1050 
Inquire(InquiryKeywordHash inquiry,char * result,std::size_t length)1051 bool InquireUnconnectedFileState::Inquire(
1052     InquiryKeywordHash inquiry, char *result, std::size_t length) {
1053   const char *str{nullptr};
1054   switch (inquiry) {
1055   case HashInquiryKeyword("ACCESS"):
1056   case HashInquiryKeyword("ACTION"):
1057   case HashInquiryKeyword("ASYNCHRONOUS"):
1058   case HashInquiryKeyword("BLANK"):
1059   case HashInquiryKeyword("CARRIAGECONTROL"):
1060   case HashInquiryKeyword("CONVERT"):
1061   case HashInquiryKeyword("DECIMAL"):
1062   case HashInquiryKeyword("DELIM"):
1063   case HashInquiryKeyword("FORM"):
1064   case HashInquiryKeyword("PAD"):
1065   case HashInquiryKeyword("POSITION"):
1066   case HashInquiryKeyword("ROUND"):
1067   case HashInquiryKeyword("SIGN"):
1068     str = "UNDEFINED";
1069     break;
1070   case HashInquiryKeyword("DIRECT"):
1071   case HashInquiryKeyword("ENCODING"):
1072   case HashInquiryKeyword("FORMATTED"):
1073   case HashInquiryKeyword("SEQUENTIAL"):
1074   case HashInquiryKeyword("STREAM"):
1075   case HashInquiryKeyword("UNFORMATTED"):
1076     str = "UNKNONN";
1077     break;
1078   case HashInquiryKeyword("READ"):
1079     str = MayRead(path_.get()) ? "YES" : "NO";
1080     break;
1081   case HashInquiryKeyword("READWRITE"):
1082     str = MayReadAndWrite(path_.get()) ? "YES" : "NO";
1083     break;
1084   case HashInquiryKeyword("WRITE"):
1085     str = MayWrite(path_.get()) ? "YES" : "NO";
1086     break;
1087   case HashInquiryKeyword("NAME"):
1088     str = path_.get();
1089     return true;
1090   }
1091   if (str) {
1092     ToFortranDefaultCharacter(result, length, str);
1093     return true;
1094   } else {
1095     BadInquiryKeywordHashCrash(inquiry);
1096     return false;
1097   }
1098 }
1099 
Inquire(InquiryKeywordHash inquiry,bool & result)1100 bool InquireUnconnectedFileState::Inquire(
1101     InquiryKeywordHash inquiry, bool &result) {
1102   switch (inquiry) {
1103   case HashInquiryKeyword("EXIST"):
1104     result = IsExtant(path_.get());
1105     return true;
1106   case HashInquiryKeyword("NAMED"):
1107     result = true;
1108     return true;
1109   case HashInquiryKeyword("OPENED"):
1110     result = false;
1111     return true;
1112   case HashInquiryKeyword("PENDING"):
1113     result = false;
1114     return true;
1115   default:
1116     BadInquiryKeywordHashCrash(inquiry);
1117     return false;
1118   }
1119 }
1120 
Inquire(InquiryKeywordHash inquiry,std::int64_t,bool & result)1121 bool InquireUnconnectedFileState::Inquire(
1122     InquiryKeywordHash inquiry, std::int64_t, bool &result) {
1123   switch (inquiry) {
1124   case HashInquiryKeyword("PENDING"):
1125     result = false;
1126     return true;
1127   default:
1128     BadInquiryKeywordHashCrash(inquiry);
1129     return false;
1130   }
1131 }
1132 
Inquire(InquiryKeywordHash inquiry,std::int64_t & result)1133 bool InquireUnconnectedFileState::Inquire(
1134     InquiryKeywordHash inquiry, std::int64_t &result) {
1135   switch (inquiry) {
1136   case HashInquiryKeyword("NEXTREC"):
1137   case HashInquiryKeyword("NUMBER"):
1138   case HashInquiryKeyword("POS"):
1139   case HashInquiryKeyword("RECL"):
1140   case HashInquiryKeyword("SIZE"):
1141     result = -1;
1142     return true;
1143   default:
1144     BadInquiryKeywordHashCrash(inquiry);
1145     return false;
1146   }
1147 }
1148 
InquireIOLengthState(const char * sourceFile,int sourceLine)1149 InquireIOLengthState::InquireIOLengthState(
1150     const char *sourceFile, int sourceLine)
1151     : NoUnitIoStatementState{sourceFile, sourceLine, *this} {}
1152 
Emit(const char *,std::size_t n,std::size_t)1153 bool InquireIOLengthState::Emit(
1154     const char *, std::size_t n, std::size_t /*elementBytes*/) {
1155   bytes_ += n;
1156   return true;
1157 }
1158 
1159 } // namespace Fortran::runtime::io
1160