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 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 35 bool IoStatementBase::Inquire(InquiryKeywordHash, bool &) { 36 Crash( 37 "IoStatementBase::Inquire() called for I/O statement other than INQUIRE"); 38 return false; 39 } 40 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 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 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> 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> 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> 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> 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> 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> 101 void InternalIoStatementState<DIR, CHAR>::BackspaceRecord() { 102 unit_.BackspaceRecord(*this); 103 } 104 105 template <Direction DIR, typename CHAR> 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> 118 void InternalIoStatementState<DIR, CHAR>::HandleAbsolutePosition( 119 std::int64_t n) { 120 return unit_.HandleAbsolutePosition(n); 121 } 122 123 template <Direction DIR, typename CHAR> 124 void InternalIoStatementState<DIR, CHAR>::HandleRelativePosition( 125 std::int64_t n) { 126 return unit_.HandleRelativePosition(n); 127 } 128 129 template <Direction DIR, typename CHAR> 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> 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> 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> 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> 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 165 ExternalIoStatementBase::ExternalIoStatementBase( 166 ExternalFileUnit &unit, const char *sourceFile, int sourceLine) 167 : IoStatementBase{sourceFile, sourceLine}, unit_{unit} {} 168 169 MutableModes &ExternalIoStatementBase::mutableModes() { return unit_.modes; } 170 171 ConnectionState &ExternalIoStatementBase::GetConnectionState() { return unit_; } 172 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 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 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 223 int CloseStatementState::EndIoStatement() { 224 int result{ExternalIoStatementBase::EndIoStatement()}; 225 unit().CloseUnit(status_, *this); 226 unit().DestroyClosed(); 227 return result; 228 } 229 230 int NoUnitIoStatementState::EndIoStatement() { 231 auto result{IoStatementBase::EndIoStatement()}; 232 FreeMemory(this); 233 return result; 234 } 235 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> 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> 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> 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> 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> 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 303 template <Direction DIR> void ExternalIoStatementState<DIR>::BackspaceRecord() { 304 unit().BackspaceRecord(*this); 305 } 306 307 template <Direction DIR> 308 void ExternalIoStatementState<DIR>::HandleAbsolutePosition(std::int64_t n) { 309 return unit().HandleAbsolutePosition(n); 310 } 311 312 template <Direction DIR> 313 void ExternalIoStatementState<DIR>::HandleRelativePosition(std::int64_t n) { 314 return unit().HandleRelativePosition(n); 315 } 316 317 template <Direction DIR> 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> 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> 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> 345 int ExternalFormattedIoStatementState<DIR, CHAR>::EndIoStatement() { 346 format_.Finish(*this); 347 return ExternalIoStatementState<DIR>::EndIoStatement(); 348 } 349 350 std::optional<DataEdit> IoStatementState::GetNextDataEdit(int n) { 351 return std::visit( 352 [&](auto &x) { return x.get().GetNextDataEdit(*this, n); }, u_); 353 } 354 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 361 std::optional<char32_t> IoStatementState::GetCurrentChar() { 362 return std::visit([&](auto &x) { return x.get().GetCurrentChar(); }, u_); 363 } 364 365 bool IoStatementState::AdvanceRecord(int n) { 366 return std::visit([=](auto &x) { return x.get().AdvanceRecord(n); }, u_); 367 } 368 369 void IoStatementState::BackspaceRecord() { 370 std::visit([](auto &x) { x.get().BackspaceRecord(); }, u_); 371 } 372 373 void IoStatementState::HandleRelativePosition(std::int64_t n) { 374 std::visit([=](auto &x) { x.get().HandleRelativePosition(n); }, u_); 375 } 376 377 int IoStatementState::EndIoStatement() { 378 return std::visit([](auto &x) { return x.get().EndIoStatement(); }, u_); 379 } 380 381 ConnectionState &IoStatementState::GetConnectionState() { 382 return std::visit( 383 [](auto &x) -> ConnectionState & { return x.get().GetConnectionState(); }, 384 u_); 385 } 386 387 MutableModes &IoStatementState::mutableModes() { 388 return std::visit( 389 [](auto &x) -> MutableModes & { return x.get().mutableModes(); }, u_); 390 } 391 392 void IoStatementState::BeginReadingRecord() { 393 std::visit([](auto &x) { return x.get().BeginReadingRecord(); }, u_); 394 } 395 396 IoErrorHandler &IoStatementState::GetIoErrorHandler() const { 397 return std::visit( 398 [](auto &x) -> IoErrorHandler & { 399 return static_cast<IoErrorHandler &>(x.get()); 400 }, 401 u_); 402 } 403 404 ExternalFileUnit *IoStatementState::GetExternalFileUnit() const { 405 return std::visit([](auto &x) { return x.get().GetExternalFileUnit(); }, u_); 406 } 407 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 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 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 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 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 512 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 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 524 bool IoStatementState::Inquire(InquiryKeywordHash inquiry, bool &out) { 525 return std::visit([&](auto &x) { return x.get().Inquire(inquiry, out); }, u_); 526 } 527 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 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 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> 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> 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> 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> 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 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 736 InquireUnitState::InquireUnitState( 737 ExternalFileUnit &unit, const char *sourceFile, int sourceLine) 738 : ExternalIoStatementBase{unit, sourceFile, sourceLine} {} 739 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 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 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 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 963 InquireNoUnitState::InquireNoUnitState(const char *sourceFile, int sourceLine) 964 : NoUnitIoStatementState{sourceFile, sourceLine, *this} {} 965 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 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 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 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 1046 InquireUnconnectedFileState::InquireUnconnectedFileState( 1047 OwningPtr<char> &&path, const char *sourceFile, int sourceLine) 1048 : NoUnitIoStatementState{sourceFile, sourceLine, *this}, path_{std::move( 1049 path)} {} 1050 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 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 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 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 1149 InquireIOLengthState::InquireIOLengthState( 1150 const char *sourceFile, int sourceLine) 1151 : NoUnitIoStatementState{sourceFile, sourceLine, *this} {} 1152 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