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, std::size_t /*elementBytes*/) { 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_ && *status_ != OpenStatus::Old) { 166 SignalError("OPEN statement for connected unit may not have STATUS= other " 167 "than 'OLD'"); 168 } 169 unit().OpenUnit(status_.value_or(OpenStatus::Unknown), action_, position_, 170 std::move(path_), pathLength_, convert_, *this); 171 return ExternalIoStatementBase::EndIoStatement(); 172 } 173 174 int CloseStatementState::EndIoStatement() { 175 int result{ExternalIoStatementBase::EndIoStatement()}; 176 unit().CloseUnit(status_, *this); 177 unit().DestroyClosed(); 178 return result; 179 } 180 181 int NoopCloseStatementState::EndIoStatement() { 182 auto result{IoStatementBase::EndIoStatement()}; 183 FreeMemory(this); 184 return result; 185 } 186 187 template <Direction DIR> int ExternalIoStatementState<DIR>::EndIoStatement() { 188 if (!unit().nonAdvancing) { 189 unit().AdvanceRecord(*this); 190 } 191 if constexpr (DIR == Direction::Output) { 192 unit().FlushIfTerminal(*this); 193 } 194 return ExternalIoStatementBase::EndIoStatement(); 195 } 196 197 template <Direction DIR> 198 bool ExternalIoStatementState<DIR>::Emit( 199 const char *data, std::size_t bytes, std::size_t elementBytes) { 200 if constexpr (DIR == Direction::Input) { 201 Crash("ExternalIoStatementState::Emit(char) called for input statement"); 202 } 203 return unit().Emit(data, bytes, elementBytes, *this); 204 } 205 206 template <Direction DIR> 207 bool ExternalIoStatementState<DIR>::Emit( 208 const char16_t *data, std::size_t chars) { 209 if constexpr (DIR == Direction::Input) { 210 Crash( 211 "ExternalIoStatementState::Emit(char16_t) called for input statement"); 212 } 213 // TODO: UTF-8 encoding 214 return unit().Emit(reinterpret_cast<const char *>(data), chars * sizeof *data, 215 static_cast<int>(sizeof *data), *this); 216 } 217 218 template <Direction DIR> 219 bool ExternalIoStatementState<DIR>::Emit( 220 const char32_t *data, std::size_t chars) { 221 if constexpr (DIR == Direction::Input) { 222 Crash( 223 "ExternalIoStatementState::Emit(char32_t) called for input statement"); 224 } 225 // TODO: UTF-8 encoding 226 return unit().Emit(reinterpret_cast<const char *>(data), chars * sizeof *data, 227 static_cast<int>(sizeof *data), *this); 228 } 229 230 template <Direction DIR> 231 std::optional<char32_t> ExternalIoStatementState<DIR>::GetCurrentChar() { 232 if constexpr (DIR == Direction::Output) { 233 Crash( 234 "ExternalIoStatementState<Direction::Output>::GetCurrentChar() called"); 235 } 236 return unit().GetCurrentChar(*this); 237 } 238 239 template <Direction DIR> 240 bool ExternalIoStatementState<DIR>::AdvanceRecord(int n) { 241 while (n-- > 0) { 242 if (!unit().AdvanceRecord(*this)) { 243 return false; 244 } 245 } 246 return true; 247 } 248 249 template <Direction DIR> void ExternalIoStatementState<DIR>::BackspaceRecord() { 250 unit().BackspaceRecord(*this); 251 } 252 253 template <Direction DIR> 254 void ExternalIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) { 255 return unit().HandleAbsolutePosition(n); 256 } 257 258 template <Direction DIR> 259 void ExternalIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) { 260 return unit().HandleRelativePosition(n); 261 } 262 263 template <Direction DIR, typename CHAR> 264 ExternalFormattedIoStatementState<DIR, CHAR>::ExternalFormattedIoStatementState( 265 ExternalFileUnit &unit, const CHAR *format, std::size_t formatLength, 266 const char *sourceFile, int sourceLine) 267 : ExternalIoStatementState<DIR>{unit, sourceFile, sourceLine}, 268 mutableModes_{unit.modes}, format_{*this, format, formatLength} {} 269 270 template <Direction DIR, typename CHAR> 271 int ExternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() { 272 format_.Finish(*this); 273 return ExternalIoStatementState<DIR>::EndIoStatement(); 274 } 275 276 std::optional<DataEdit> IoStatementState::GetNextDataEdit(int n) { 277 return std::visit( 278 [&](auto &x) { return x.get().GetNextDataEdit(*this, n); }, u_); 279 } 280 281 bool IoStatementState::Emit( 282 const char *data, std::size_t n, std::size_t elementBytes) { 283 return std::visit( 284 [=](auto &x) { return x.get().Emit(data, n, elementBytes); }, u_); 285 } 286 287 std::optional<char32_t> IoStatementState::GetCurrentChar() { 288 return std::visit([&](auto &x) { return x.get().GetCurrentChar(); }, u_); 289 } 290 291 bool IoStatementState::AdvanceRecord(int n) { 292 return std::visit([=](auto &x) { return x.get().AdvanceRecord(n); }, u_); 293 } 294 295 void IoStatementState::BackspaceRecord() { 296 std::visit([](auto &x) { x.get().BackspaceRecord(); }, u_); 297 } 298 299 void IoStatementState::HandleRelativePosition(std::int64_t n) { 300 std::visit([=](auto &x) { x.get().HandleRelativePosition(n); }, u_); 301 } 302 303 int IoStatementState::EndIoStatement() { 304 return std::visit([](auto &x) { return x.get().EndIoStatement(); }, u_); 305 } 306 307 ConnectionState &IoStatementState::GetConnectionState() { 308 return std::visit( 309 [](auto &x) -> ConnectionState & { return x.get().GetConnectionState(); }, 310 u_); 311 } 312 313 MutableModes &IoStatementState::mutableModes() { 314 return std::visit( 315 [](auto &x) -> MutableModes & { return x.get().mutableModes(); }, u_); 316 } 317 318 IoErrorHandler &IoStatementState::GetIoErrorHandler() const { 319 return std::visit( 320 [](auto &x) -> IoErrorHandler & { 321 return static_cast<IoErrorHandler &>(x.get()); 322 }, 323 u_); 324 } 325 326 ExternalFileUnit *IoStatementState::GetExternalFileUnit() const { 327 return std::visit([](auto &x) { return x.get().GetExternalFileUnit(); }, u_); 328 } 329 330 bool IoStatementState::EmitRepeated(char ch, std::size_t n) { 331 return std::visit( 332 [=](auto &x) { 333 for (std::size_t j{0}; j < n; ++j) { 334 if (!x.get().Emit(&ch, 1)) { 335 return false; 336 } 337 } 338 return true; 339 }, 340 u_); 341 } 342 343 bool IoStatementState::EmitField( 344 const char *p, std::size_t length, std::size_t width) { 345 if (width <= 0) { 346 width = static_cast<int>(length); 347 } 348 if (length > static_cast<std::size_t>(width)) { 349 return EmitRepeated('*', width); 350 } else { 351 return EmitRepeated(' ', static_cast<int>(width - length)) && 352 Emit(p, length); 353 } 354 } 355 356 std::optional<char32_t> IoStatementState::SkipSpaces( 357 std::optional<int> &remaining) { 358 while (!remaining || *remaining > 0) { 359 if (auto ch{GetCurrentChar()}) { 360 if (*ch != ' ' && *ch != '\t') { 361 return ch; 362 } 363 HandleRelativePosition(1); 364 if (remaining) { 365 --*remaining; 366 } 367 } else { 368 break; 369 } 370 } 371 return std::nullopt; 372 } 373 374 std::optional<char32_t> IoStatementState::NextInField( 375 std::optional<int> &remaining) { 376 if (!remaining) { // list-directed or namelist: check for separators 377 if (auto next{GetCurrentChar()}) { 378 switch (*next) { 379 case ' ': 380 case '\t': 381 case ',': 382 case ';': 383 case '/': 384 case '(': 385 case ')': 386 case '\'': 387 case '"': 388 case '*': 389 case '\n': // for stream access 390 break; 391 default: 392 HandleRelativePosition(1); 393 return next; 394 } 395 } 396 } else if (*remaining > 0) { 397 if (auto next{GetCurrentChar()}) { 398 --*remaining; 399 HandleRelativePosition(1); 400 return next; 401 } 402 const ConnectionState &connection{GetConnectionState()}; 403 if (!connection.IsAtEOF() && connection.isFixedRecordLength && 404 connection.recordLength && 405 connection.positionInRecord >= *connection.recordLength) { 406 if (connection.modes.pad) { // PAD='YES' 407 --*remaining; 408 return std::optional<char32_t>{' '}; 409 } 410 IoErrorHandler &handler{GetIoErrorHandler()}; 411 if (connection.nonAdvancing) { 412 handler.SignalEor(); 413 } else { 414 handler.SignalError(IostatRecordReadOverrun); 415 } 416 } 417 } 418 return std::nullopt; 419 } 420 421 std::optional<char32_t> IoStatementState::GetNextNonBlank() { 422 auto ch{GetCurrentChar()}; 423 while (!ch || *ch == ' ' || *ch == '\t') { 424 if (ch) { 425 HandleRelativePosition(1); 426 } else if (!AdvanceRecord()) { 427 return std::nullopt; 428 } 429 ch = GetCurrentChar(); 430 } 431 return ch; 432 } 433 434 bool ListDirectedStatementState<Direction::Output>::NeedAdvance( 435 const ConnectionState &connection, std::size_t width) const { 436 return connection.positionInRecord > 0 && 437 width > connection.RemainingSpaceInRecord(); 438 } 439 440 bool ListDirectedStatementState<Direction::Output>::EmitLeadingSpaceOrAdvance( 441 IoStatementState &io, std::size_t length, bool isCharacter) { 442 if (length == 0) { 443 return true; 444 } 445 const ConnectionState &connection{io.GetConnectionState()}; 446 int space{connection.positionInRecord == 0 || 447 !(isCharacter && lastWasUndelimitedCharacter)}; 448 lastWasUndelimitedCharacter = false; 449 if (NeedAdvance(connection, space + length)) { 450 return io.AdvanceRecord(); 451 } 452 if (space) { 453 return io.Emit(" ", 1); 454 } 455 return true; 456 } 457 458 std::optional<DataEdit> 459 ListDirectedStatementState<Direction::Output>::GetNextDataEdit( 460 IoStatementState &io, int maxRepeat) { 461 DataEdit edit; 462 edit.descriptor = DataEdit::ListDirected; 463 edit.repeat = maxRepeat; 464 edit.modes = io.mutableModes(); 465 return edit; 466 } 467 468 std::optional<DataEdit> 469 ListDirectedStatementState<Direction::Input>::GetNextDataEdit( 470 IoStatementState &io, int maxRepeat) { 471 // N.B. list-directed transfers cannot be nonadvancing (C1221) 472 ConnectionState &connection{io.GetConnectionState()}; 473 DataEdit edit; 474 edit.descriptor = DataEdit::ListDirected; 475 edit.repeat = 1; // may be overridden below 476 edit.modes = connection.modes; 477 if (hitSlash_) { // everything after '/' is nullified 478 edit.descriptor = DataEdit::ListDirectedNullValue; 479 return edit; 480 } 481 char32_t comma{','}; 482 if (io.mutableModes().editingFlags & decimalComma) { 483 comma = ';'; 484 } 485 if (remaining_ > 0 && !realPart_) { // "r*c" repetition in progress 486 while (connection.currentRecordNumber > initialRecordNumber_) { 487 io.BackspaceRecord(); 488 } 489 connection.HandleAbsolutePosition(initialPositionInRecord_); 490 if (!imaginaryPart_) { 491 edit.repeat = std::min<int>(remaining_, maxRepeat); 492 auto ch{io.GetNextNonBlank()}; 493 if (!ch || *ch == ' ' || *ch == '\t' || *ch == comma) { 494 // "r*" repeated null 495 edit.descriptor = DataEdit::ListDirectedNullValue; 496 } 497 } 498 remaining_ -= edit.repeat; 499 return edit; 500 } 501 // Skip separators, handle a "r*c" repeat count; see 13.10.2 in Fortran 2018 502 auto ch{io.GetNextNonBlank()}; 503 if (imaginaryPart_) { 504 imaginaryPart_ = false; 505 if (ch && *ch == ')') { 506 io.HandleRelativePosition(1); 507 ch = io.GetNextNonBlank(); 508 } 509 } else if (realPart_) { 510 realPart_ = false; 511 imaginaryPart_ = true; 512 edit.descriptor = DataEdit::ListDirectedImaginaryPart; 513 } 514 if (!ch) { 515 return std::nullopt; 516 } 517 if (*ch == '/') { 518 hitSlash_ = true; 519 edit.descriptor = DataEdit::ListDirectedNullValue; 520 return edit; 521 } 522 bool isFirstItem{isFirstItem_}; 523 isFirstItem_ = false; 524 if (*ch == comma) { 525 if (isFirstItem) { 526 edit.descriptor = DataEdit::ListDirectedNullValue; 527 return edit; 528 } 529 // Consume comma & whitespace after previous item. 530 io.HandleRelativePosition(1); 531 ch = io.GetNextNonBlank(); 532 if (!ch) { 533 return std::nullopt; 534 } 535 if (*ch == comma || *ch == '/') { 536 edit.descriptor = DataEdit::ListDirectedNullValue; 537 return edit; 538 } 539 } 540 if (imaginaryPart_) { // can't repeat components 541 return edit; 542 } 543 if (*ch >= '0' && *ch <= '9') { // look for "r*" repetition count 544 auto start{connection.positionInRecord}; 545 int r{0}; 546 do { 547 static auto constexpr clamp{(std::numeric_limits<int>::max() - '9') / 10}; 548 if (r >= clamp) { 549 r = 0; 550 break; 551 } 552 r = 10 * r + (*ch - '0'); 553 io.HandleRelativePosition(1); 554 ch = io.GetCurrentChar(); 555 } while (ch && *ch >= '0' && *ch <= '9'); 556 if (r > 0 && ch && *ch == '*') { // subtle: r must be nonzero 557 io.HandleRelativePosition(1); 558 ch = io.GetCurrentChar(); 559 if (ch && *ch == '/') { // r*/ 560 hitSlash_ = true; 561 edit.descriptor = DataEdit::ListDirectedNullValue; 562 return edit; 563 } 564 if (!ch || *ch == ' ' || *ch == '\t' || *ch == comma) { // "r*" null 565 edit.descriptor = DataEdit::ListDirectedNullValue; 566 } 567 edit.repeat = std::min<int>(r, maxRepeat); 568 remaining_ = r - edit.repeat; 569 initialRecordNumber_ = connection.currentRecordNumber; 570 initialPositionInRecord_ = connection.positionInRecord; 571 } else { // not a repetition count, just an integer value; rewind 572 connection.positionInRecord = start; 573 } 574 } 575 if (!imaginaryPart_ && ch && *ch == '(') { 576 realPart_ = true; 577 io.HandleRelativePosition(1); 578 edit.descriptor = DataEdit::ListDirectedRealPart; 579 } 580 return edit; 581 } 582 583 template <Direction DIR> 584 bool UnformattedIoStatementState<DIR>::Receive( 585 char *data, std::size_t bytes, std::size_t elementBytes) { 586 if constexpr (DIR == Direction::Output) { 587 this->Crash( 588 "UnformattedIoStatementState::Receive() called for output statement"); 589 } 590 return this->unit().Receive(data, bytes, elementBytes, *this); 591 } 592 593 template <Direction DIR> 594 bool UnformattedIoStatementState<DIR>::Emit( 595 const char *data, std::size_t bytes, std::size_t elementBytes) { 596 if constexpr (DIR == Direction::Input) { 597 this->Crash( 598 "UnformattedIoStatementState::Emit() called for input statement"); 599 } 600 return ExternalIoStatementState<DIR>::Emit(data, bytes, elementBytes); 601 } 602 603 template <Direction DIR> 604 int UnformattedIoStatementState<DIR>::EndIoStatement() { 605 ExternalFileUnit &unit{this->unit()}; 606 if constexpr (DIR == Direction::Output) { 607 if (unit.access == Access::Sequential && !unit.isFixedRecordLength) { 608 // Append the length of a sequential unformatted variable-length record 609 // as its footer, then overwrite the reserved first four bytes of the 610 // record with its length as its header. These four bytes were skipped 611 // over in BeginUnformattedOutput(). 612 // TODO: Break very large records up into subrecords with negative 613 // headers &/or footers 614 union { 615 std::uint32_t u; 616 char c[sizeof u]; 617 } u; 618 u.u = unit.furthestPositionInRecord - sizeof u; 619 // TODO: Convert record length to little-endian on big-endian host? 620 if (!(this->Emit(u.c, sizeof u) && 621 (this->HandleAbsolutePosition(0), this->Emit(u.c, sizeof u)))) { 622 return false; 623 } 624 } 625 } 626 return ExternalIoStatementState<DIR>::EndIoStatement(); 627 } 628 629 template class InternalIoStatementState<Direction::Output>; 630 template class InternalIoStatementState<Direction::Input>; 631 template class InternalFormattedIoStatementState<Direction::Output>; 632 template class InternalFormattedIoStatementState<Direction::Input>; 633 template class InternalListIoStatementState<Direction::Output>; 634 template class InternalListIoStatementState<Direction::Input>; 635 template class ExternalIoStatementState<Direction::Output>; 636 template class ExternalIoStatementState<Direction::Input>; 637 template class ExternalFormattedIoStatementState<Direction::Output>; 638 template class ExternalFormattedIoStatementState<Direction::Input>; 639 template class ExternalListIoStatementState<Direction::Output>; 640 template class ExternalListIoStatementState<Direction::Input>; 641 template class UnformattedIoStatementState<Direction::Output>; 642 template class UnformattedIoStatementState<Direction::Input>; 643 644 int ExternalMiscIoStatementState::EndIoStatement() { 645 ExternalFileUnit &ext{unit()}; 646 switch (which_) { 647 case Flush: 648 ext.Flush(*this); 649 std::fflush(nullptr); // flushes C stdio output streams (12.9(2)) 650 break; 651 case Backspace: 652 ext.BackspaceRecord(*this); 653 break; 654 case Endfile: 655 ext.Endfile(*this); 656 break; 657 case Rewind: 658 ext.Rewind(*this); 659 break; 660 } 661 return ExternalIoStatementBase::EndIoStatement(); 662 } 663 664 } // namespace Fortran::runtime::io 665