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