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 
22 int IoStatementBase::EndIoStatement() { return GetIoStat(); }
23 
24 std::optional<DataEdit> IoStatementBase::GetNextDataEdit(
25     IoStatementState &, int) {
26   return std::nullopt;
27 }
28 
29 template <Direction DIR, typename CHAR>
30 InternalIoStatementState<DIR, CHAR>::InternalIoStatementState(
31     Buffer scalar, std::size_t length, const char *sourceFile, int sourceLine)
32     : IoStatementBase{sourceFile, sourceLine}, unit_{scalar, length} {}
33 
34 template <Direction DIR, typename CHAR>
35 InternalIoStatementState<DIR, CHAR>::InternalIoStatementState(
36     const Descriptor &d, const char *sourceFile, int sourceLine)
37     : IoStatementBase{sourceFile, sourceLine}, unit_{d, *this} {}
38 
39 template <Direction DIR, typename CHAR>
40 bool InternalIoStatementState<DIR, CHAR>::Emit(
41     const CharType *data, std::size_t chars) {
42   if constexpr (DIR == Direction::Input) {
43     Crash("InternalIoStatementState<Direction::Input>::Emit() called");
44     return false;
45   }
46   return unit_.Emit(data, chars, *this);
47 }
48 
49 template <Direction DIR, typename CHAR>
50 std::optional<char32_t> InternalIoStatementState<DIR, CHAR>::GetCurrentChar() {
51   if constexpr (DIR == Direction::Output) {
52     Crash(
53         "InternalIoStatementState<Direction::Output>::GetCurrentChar() called");
54     return std::nullopt;
55   }
56   return unit_.GetCurrentChar(*this);
57 }
58 
59 template <Direction DIR, typename CHAR>
60 bool InternalIoStatementState<DIR, CHAR>::AdvanceRecord(int n) {
61   while (n-- > 0) {
62     if (!unit_.AdvanceRecord(*this)) {
63       return false;
64     }
65   }
66   return true;
67 }
68 
69 template <Direction DIR, typename CHAR>
70 void InternalIoStatementState<DIR, CHAR>::BackspaceRecord() {
71   unit_.BackspaceRecord(*this);
72 }
73 
74 template <Direction DIR, typename CHAR>
75 int InternalIoStatementState<DIR, CHAR>::EndIoStatement() {
76   if constexpr (DIR == Direction::Output) {
77     unit_.EndIoStatement(); // fill
78   }
79   auto result{IoStatementBase::EndIoStatement()};
80   if (free_) {
81     FreeMemory(this);
82   }
83   return result;
84 }
85 
86 template <Direction DIR, typename CHAR>
87 void InternalIoStatementState<DIR, CHAR>::HandleAbsolutePosition(
88     std::int64_t n) {
89   return unit_.HandleAbsolutePosition(n);
90 }
91 
92 template <Direction DIR, typename CHAR>
93 void InternalIoStatementState<DIR, CHAR>::HandleRelativePosition(
94     std::int64_t n) {
95   return unit_.HandleRelativePosition(n);
96 }
97 
98 template <Direction DIR, typename CHAR>
99 InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState(
100     Buffer buffer, std::size_t length, const CHAR *format,
101     std::size_t formatLength, const char *sourceFile, int sourceLine)
102     : InternalIoStatementState<DIR, CHAR>{buffer, length, sourceFile,
103           sourceLine},
104       ioStatementState_{*this}, format_{*this, format, formatLength} {}
105 
106 template <Direction DIR, typename CHAR>
107 InternalFormattedIoStatementState<DIR, CHAR>::InternalFormattedIoStatementState(
108     const Descriptor &d, const CHAR *format, std::size_t formatLength,
109     const char *sourceFile, int sourceLine)
110     : InternalIoStatementState<DIR, CHAR>{d, sourceFile, sourceLine},
111       ioStatementState_{*this}, format_{*this, format, formatLength} {}
112 
113 template <Direction DIR, typename CHAR>
114 int InternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
115   if constexpr (DIR == Direction::Output) {
116     format_.Finish(*this); // ignore any remaining input positioning actions
117   }
118   return InternalIoStatementState<DIR, CHAR>::EndIoStatement();
119 }
120 
121 template <Direction DIR, typename CHAR>
122 InternalListIoStatementState<DIR, CHAR>::InternalListIoStatementState(
123     Buffer buffer, std::size_t length, const char *sourceFile, int sourceLine)
124     : InternalIoStatementState<DIR, CharType>{buffer, length, sourceFile,
125           sourceLine},
126       ioStatementState_{*this} {}
127 
128 template <Direction DIR, typename CHAR>
129 InternalListIoStatementState<DIR, CHAR>::InternalListIoStatementState(
130     const Descriptor &d, const char *sourceFile, int sourceLine)
131     : InternalIoStatementState<DIR, CharType>{d, sourceFile, sourceLine},
132       ioStatementState_{*this} {}
133 
134 ExternalIoStatementBase::ExternalIoStatementBase(
135     ExternalFileUnit &unit, const char *sourceFile, int sourceLine)
136     : IoStatementBase{sourceFile, sourceLine}, unit_{unit} {}
137 
138 MutableModes &ExternalIoStatementBase::mutableModes() { return unit_.modes; }
139 
140 ConnectionState &ExternalIoStatementBase::GetConnectionState() { return unit_; }
141 
142 int ExternalIoStatementBase::EndIoStatement() {
143   if (unit_.nonAdvancing) {
144     unit_.leftTabLimit = unit_.furthestPositionInRecord;
145     unit_.nonAdvancing = false;
146   } else {
147     unit_.leftTabLimit.reset();
148   }
149   auto result{IoStatementBase::EndIoStatement()};
150   unit_.EndIoStatement(); // annihilates *this in unit_.u_
151   return result;
152 }
153 
154 void OpenStatementState::set_path(
155     const char *path, std::size_t length, int kind) {
156   if (kind != 1) { // TODO
157     Crash("OPEN: FILE= with unimplemented: CHARACTER(KIND=%d)", kind);
158   }
159   std::size_t bytes{length * kind}; // TODO: UTF-8 encoding of Unicode path
160   path_ = SaveDefaultCharacter(path, bytes, *this);
161   pathLength_ = length;
162 }
163 
164 int OpenStatementState::EndIoStatement() {
165   if (wasExtant_ && status_ != OpenStatus::Old) {
166     SignalError("OPEN statement for connected unit must have STATUS='OLD'");
167   }
168   unit().OpenUnit(status_, position_, std::move(path_), pathLength_, *this);
169   return ExternalIoStatementBase::EndIoStatement();
170 }
171 
172 int CloseStatementState::EndIoStatement() {
173   int result{ExternalIoStatementBase::EndIoStatement()};
174   unit().CloseUnit(status_, *this);
175   unit().DestroyClosed();
176   return result;
177 }
178 
179 int NoopCloseStatementState::EndIoStatement() {
180   auto result{IoStatementBase::EndIoStatement()};
181   FreeMemory(this);
182   return result;
183 }
184 
185 template <Direction DIR> int ExternalIoStatementState<DIR>::EndIoStatement() {
186   if (!unit().nonAdvancing) {
187     unit().AdvanceRecord(*this);
188   }
189   if constexpr (DIR == Direction::Output) {
190     unit().FlushIfTerminal(*this);
191   }
192   return ExternalIoStatementBase::EndIoStatement();
193 }
194 
195 template <Direction DIR>
196 bool ExternalIoStatementState<DIR>::Emit(const char *data, std::size_t chars) {
197   if constexpr (DIR == Direction::Input) {
198     Crash("ExternalIoStatementState::Emit(char) called for input statement");
199   }
200   return unit().Emit(data, chars * sizeof(*data), *this);
201 }
202 
203 template <Direction DIR>
204 bool ExternalIoStatementState<DIR>::Emit(
205     const char16_t *data, std::size_t chars) {
206   if constexpr (DIR == Direction::Input) {
207     Crash(
208         "ExternalIoStatementState::Emit(char16_t) called for input statement");
209   }
210   // TODO: UTF-8 encoding
211   return unit().Emit(
212       reinterpret_cast<const char *>(data), chars * sizeof(*data), *this);
213 }
214 
215 template <Direction DIR>
216 bool ExternalIoStatementState<DIR>::Emit(
217     const char32_t *data, std::size_t chars) {
218   if constexpr (DIR == Direction::Input) {
219     Crash(
220         "ExternalIoStatementState::Emit(char32_t) called for input statement");
221   }
222   // TODO: UTF-8 encoding
223   return unit().Emit(
224       reinterpret_cast<const char *>(data), chars * sizeof(*data), *this);
225 }
226 
227 template <Direction DIR>
228 std::optional<char32_t> ExternalIoStatementState<DIR>::GetCurrentChar() {
229   if constexpr (DIR == Direction::Output) {
230     Crash(
231         "ExternalIoStatementState<Direction::Output>::GetCurrentChar() called");
232   }
233   return unit().GetCurrentChar(*this);
234 }
235 
236 template <Direction DIR>
237 bool ExternalIoStatementState<DIR>::AdvanceRecord(int n) {
238   while (n-- > 0) {
239     if (!unit().AdvanceRecord(*this)) {
240       return false;
241     }
242   }
243   return true;
244 }
245 
246 template <Direction DIR> void ExternalIoStatementState<DIR>::BackspaceRecord() {
247   unit().BackspaceRecord(*this);
248 }
249 
250 template <Direction DIR>
251 void ExternalIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) {
252   return unit().HandleAbsolutePosition(n);
253 }
254 
255 template <Direction DIR>
256 void ExternalIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) {
257   return unit().HandleRelativePosition(n);
258 }
259 
260 template <Direction DIR, typename CHAR>
261 ExternalFormattedIoStatementState<DIR, CHAR>::ExternalFormattedIoStatementState(
262     ExternalFileUnit &unit, const CHAR *format, std::size_t formatLength,
263     const char *sourceFile, int sourceLine)
264     : ExternalIoStatementState<DIR>{unit, sourceFile, sourceLine},
265       mutableModes_{unit.modes}, format_{*this, format, formatLength} {}
266 
267 template <Direction DIR, typename CHAR>
268 int ExternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() {
269   format_.Finish(*this);
270   return ExternalIoStatementState<DIR>::EndIoStatement();
271 }
272 
273 std::optional<DataEdit> IoStatementState::GetNextDataEdit(int n) {
274   return std::visit(
275       [&](auto &x) { return x.get().GetNextDataEdit(*this, n); }, u_);
276 }
277 
278 bool IoStatementState::Emit(const char *data, std::size_t n) {
279   return std::visit([=](auto &x) { return x.get().Emit(data, n); }, u_);
280 }
281 
282 std::optional<char32_t> IoStatementState::GetCurrentChar() {
283   return std::visit([&](auto &x) { return x.get().GetCurrentChar(); }, u_);
284 }
285 
286 bool IoStatementState::AdvanceRecord(int n) {
287   return std::visit([=](auto &x) { return x.get().AdvanceRecord(n); }, u_);
288 }
289 
290 void IoStatementState::BackspaceRecord() {
291   std::visit([](auto &x) { x.get().BackspaceRecord(); }, u_);
292 }
293 
294 void IoStatementState::HandleRelativePosition(std::int64_t n) {
295   std::visit([=](auto &x) { x.get().HandleRelativePosition(n); }, u_);
296 }
297 
298 int IoStatementState::EndIoStatement() {
299   return std::visit([](auto &x) { return x.get().EndIoStatement(); }, u_);
300 }
301 
302 ConnectionState &IoStatementState::GetConnectionState() {
303   return std::visit(
304       [](auto &x) -> ConnectionState & { return x.get().GetConnectionState(); },
305       u_);
306 }
307 
308 MutableModes &IoStatementState::mutableModes() {
309   return std::visit(
310       [](auto &x) -> MutableModes & { return x.get().mutableModes(); }, u_);
311 }
312 
313 IoErrorHandler &IoStatementState::GetIoErrorHandler() const {
314   return std::visit(
315       [](auto &x) -> IoErrorHandler & {
316         return static_cast<IoErrorHandler &>(x.get());
317       },
318       u_);
319 }
320 
321 ExternalFileUnit *IoStatementState::GetExternalFileUnit() const {
322   return std::visit([](auto &x) { return x.get().GetExternalFileUnit(); }, u_);
323 }
324 
325 bool IoStatementState::EmitRepeated(char ch, std::size_t n) {
326   return std::visit(
327       [=](auto &x) {
328         for (std::size_t j{0}; j < n; ++j) {
329           if (!x.get().Emit(&ch, 1)) {
330             return false;
331           }
332         }
333         return true;
334       },
335       u_);
336 }
337 
338 bool IoStatementState::EmitField(
339     const char *p, std::size_t length, std::size_t width) {
340   if (width <= 0) {
341     width = static_cast<int>(length);
342   }
343   if (length > static_cast<std::size_t>(width)) {
344     return EmitRepeated('*', width);
345   } else {
346     return EmitRepeated(' ', static_cast<int>(width - length)) &&
347         Emit(p, length);
348   }
349 }
350 
351 std::optional<char32_t> IoStatementState::SkipSpaces(
352     std::optional<int> &remaining) {
353   while (!remaining || *remaining > 0) {
354     if (auto ch{GetCurrentChar()}) {
355       if (*ch != ' ') {
356         return ch;
357       }
358       HandleRelativePosition(1);
359       if (remaining) {
360         --*remaining;
361       }
362     } else {
363       break;
364     }
365   }
366   return std::nullopt;
367 }
368 
369 std::optional<char32_t> IoStatementState::NextInField(
370     std::optional<int> &remaining) {
371   if (!remaining) { // list-directed or namelist: check for separators
372     if (auto next{GetCurrentChar()}) {
373       switch (*next) {
374       case ' ':
375       case ',':
376       case ';':
377       case '/':
378       case '(':
379       case ')':
380       case '\'':
381       case '"':
382       case '*':
383       case '\n': // for stream access
384         break;
385       default:
386         HandleRelativePosition(1);
387         return next;
388       }
389     }
390   } else if (*remaining > 0) {
391     if (auto next{GetCurrentChar()}) {
392       --*remaining;
393       HandleRelativePosition(1);
394       return next;
395     }
396     const ConnectionState &connection{GetConnectionState()};
397     if (!connection.IsAtEOF() && connection.isFixedRecordLength &&
398         connection.recordLength &&
399         connection.positionInRecord >= *connection.recordLength) {
400       if (connection.modes.pad) { // PAD='YES'
401         --*remaining;
402         return std::optional<char32_t>{' '};
403       }
404       IoErrorHandler &handler{GetIoErrorHandler()};
405       if (connection.nonAdvancing) {
406         handler.SignalEor();
407       } else {
408         handler.SignalError(IostatRecordReadOverrun);
409       }
410     }
411   }
412   return std::nullopt;
413 }
414 
415 std::optional<char32_t> IoStatementState::GetNextNonBlank() {
416   auto ch{GetCurrentChar()};
417   while (ch.value_or(' ') == ' ') {
418     if (ch) {
419       HandleRelativePosition(1);
420     } else if (!AdvanceRecord()) {
421       return std::nullopt;
422     }
423     ch = GetCurrentChar();
424   }
425   return ch;
426 }
427 
428 bool ListDirectedStatementState<Direction::Output>::NeedAdvance(
429     const ConnectionState &connection, std::size_t width) const {
430   return connection.positionInRecord > 0 &&
431       width > connection.RemainingSpaceInRecord();
432 }
433 
434 bool ListDirectedStatementState<Direction::Output>::EmitLeadingSpaceOrAdvance(
435     IoStatementState &io, std::size_t length, bool isCharacter) {
436   if (length == 0) {
437     return true;
438   }
439   const ConnectionState &connection{io.GetConnectionState()};
440   int space{connection.positionInRecord == 0 ||
441       !(isCharacter && lastWasUndelimitedCharacter)};
442   lastWasUndelimitedCharacter = false;
443   if (NeedAdvance(connection, space + length)) {
444     return io.AdvanceRecord();
445   }
446   if (space) {
447     return io.Emit(" ", 1);
448   }
449   return true;
450 }
451 
452 std::optional<DataEdit>
453 ListDirectedStatementState<Direction::Output>::GetNextDataEdit(
454     IoStatementState &io, int maxRepeat) {
455   DataEdit edit;
456   edit.descriptor = DataEdit::ListDirected;
457   edit.repeat = maxRepeat;
458   edit.modes = io.mutableModes();
459   return edit;
460 }
461 
462 std::optional<DataEdit>
463 ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
464     IoStatementState &io, int maxRepeat) {
465   // N.B. list-directed transfers cannot be nonadvancing (C1221)
466   ConnectionState &connection{io.GetConnectionState()};
467   DataEdit edit;
468   edit.descriptor = DataEdit::ListDirected;
469   edit.repeat = 1; // may be overridden below
470   edit.modes = connection.modes;
471   if (hitSlash_) { // everything after '/' is nullified
472     edit.descriptor = DataEdit::ListDirectedNullValue;
473     return edit;
474   }
475   if (remaining_ > 0 && !realPart_) { // "r*c" repetition in progress
476     while (connection.currentRecordNumber > initialRecordNumber_) {
477       io.BackspaceRecord();
478     }
479     connection.HandleAbsolutePosition(initialPositionInRecord_);
480     if (!imaginaryPart_) {
481       edit.repeat = std::min<int>(remaining_, maxRepeat);
482     }
483     remaining_ -= edit.repeat;
484     return edit;
485   }
486   // Skip separators, handle a "r*c" repeat count; see 13.10.2 in Fortran 2018
487   auto ch{io.GetNextNonBlank()};
488   if (imaginaryPart_) {
489     imaginaryPart_ = false;
490     if (ch && *ch == ')') {
491       io.HandleRelativePosition(1);
492       ch = io.GetNextNonBlank();
493     }
494   } else if (realPart_) {
495     realPart_ = false;
496     imaginaryPart_ = true;
497   }
498   if (!ch) {
499     return std::nullopt;
500   }
501   if (*ch == '/') {
502     hitSlash_ = true;
503     edit.descriptor = DataEdit::ListDirectedNullValue;
504     return edit;
505   }
506   char32_t comma{','};
507   if (io.mutableModes().editingFlags & decimalComma) {
508     comma = ';';
509   }
510   bool isFirstItem{isFirstItem_};
511   isFirstItem_ = false;
512   if (*ch == comma) {
513     if (isFirstItem) {
514       edit.descriptor = DataEdit::ListDirectedNullValue;
515       return edit;
516     }
517     // Consume comma & whitespace after previous item.
518     io.HandleRelativePosition(1);
519     ch = io.GetNextNonBlank();
520     if (!ch) {
521       return std::nullopt;
522     }
523     if (*ch == comma || *ch == '/') {
524       edit.descriptor = DataEdit::ListDirectedNullValue;
525       return edit;
526     }
527   }
528   if (imaginaryPart_) { // can't repeat components
529     return edit;
530   }
531   if (*ch >= '0' && *ch <= '9') { // look for "r*" repetition count
532     auto start{connection.positionInRecord};
533     int r{0};
534     do {
535       static auto constexpr clamp{(std::numeric_limits<int>::max() - '9') / 10};
536       if (r >= clamp) {
537         r = 0;
538         break;
539       }
540       r = 10 * r + (*ch - '0');
541       io.HandleRelativePosition(1);
542       ch = io.GetCurrentChar();
543     } while (ch && *ch >= '0' && *ch <= '9');
544     if (r > 0 && ch && *ch == '*') { // subtle: r must be nonzero
545       io.HandleRelativePosition(1);
546       ch = io.GetCurrentChar();
547       if (!ch || *ch == ' ' || *ch == comma || *ch == '/') { // "r*" null
548         edit.descriptor = DataEdit::ListDirectedNullValue;
549         return edit;
550       }
551       edit.repeat = std::min<int>(r, maxRepeat);
552       remaining_ = r - edit.repeat;
553       initialRecordNumber_ = connection.currentRecordNumber;
554       initialPositionInRecord_ = connection.positionInRecord;
555     } else { // not a repetition count, just an integer value; rewind
556       connection.positionInRecord = start;
557     }
558   }
559   if (!imaginaryPart_ && ch && *ch == '(') {
560     realPart_ = true;
561     io.HandleRelativePosition(1);
562   }
563   return edit;
564 }
565 
566 template <Direction DIR>
567 bool UnformattedIoStatementState<DIR>::Receive(char *data, std::size_t bytes) {
568   if constexpr (DIR == Direction::Output) {
569     this->Crash(
570         "UnformattedIoStatementState::Receive() called for output statement");
571   }
572   return this->unit().Receive(data, bytes, *this);
573 }
574 
575 template <Direction DIR>
576 int UnformattedIoStatementState<DIR>::EndIoStatement() {
577   ExternalFileUnit &unit{this->unit()};
578   if constexpr (DIR == Direction::Output) {
579     if (unit.access == Access::Sequential && !unit.isFixedRecordLength) {
580       // Append the length of a sequential unformatted variable-length record
581       // as its footer, then overwrite the reserved first four bytes of the
582       // record with its length as its header.  These four bytes were skipped
583       // over in BeginUnformattedOutput().
584       // TODO: Break very large records up into subrecords with negative
585       // headers &/or footers
586       union {
587         std::uint32_t u;
588         char c[sizeof u];
589       } u;
590       u.u = unit.furthestPositionInRecord - sizeof u;
591       // TODO: Convert record length to little-endian on big-endian host?
592       if (!(this->Emit(u.c, sizeof u) &&
593               (this->HandleAbsolutePosition(0), this->Emit(u.c, sizeof u)))) {
594         return false;
595       }
596     }
597   }
598   return ExternalIoStatementState<DIR>::EndIoStatement();
599 }
600 
601 template class InternalIoStatementState<Direction::Output>;
602 template class InternalIoStatementState<Direction::Input>;
603 template class InternalFormattedIoStatementState<Direction::Output>;
604 template class InternalFormattedIoStatementState<Direction::Input>;
605 template class InternalListIoStatementState<Direction::Output>;
606 template class InternalListIoStatementState<Direction::Input>;
607 template class ExternalIoStatementState<Direction::Output>;
608 template class ExternalIoStatementState<Direction::Input>;
609 template class ExternalFormattedIoStatementState<Direction::Output>;
610 template class ExternalFormattedIoStatementState<Direction::Input>;
611 template class ExternalListIoStatementState<Direction::Output>;
612 template class ExternalListIoStatementState<Direction::Input>;
613 template class UnformattedIoStatementState<Direction::Output>;
614 template class UnformattedIoStatementState<Direction::Input>;
615 
616 int ExternalMiscIoStatementState::EndIoStatement() {
617   ExternalFileUnit &ext{unit()};
618   switch (which_) {
619   case Flush:
620     ext.Flush(*this);
621     std::fflush(nullptr); // flushes C stdio output streams (12.9(2))
622     break;
623   case Backspace:
624     ext.BackspaceRecord(*this);
625     break;
626   case Endfile:
627     ext.Endfile(*this);
628     break;
629   case Rewind:
630     ext.Rewind(*this);
631     break;
632   }
633   return ExternalIoStatementBase::EndIoStatement();
634 }
635 
636 } // namespace Fortran::runtime::io
637