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