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