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