1 //===-- runtime/io-api.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 // Implements the I/O statement API 10 11 #include "io-api.h" 12 #include "descriptor-io.h" 13 #include "descriptor.h" 14 #include "edit-input.h" 15 #include "edit-output.h" 16 #include "environment.h" 17 #include "format.h" 18 #include "io-stmt.h" 19 #include "memory.h" 20 #include "terminator.h" 21 #include "tools.h" 22 #include "unit.h" 23 #include <cstdlib> 24 #include <memory> 25 26 namespace Fortran::runtime::io { 27 28 const char *InquiryKeywordHashDecode( 29 char *buffer, std::size_t n, InquiryKeywordHash hash) { 30 if (n < 1) { 31 return nullptr; 32 } 33 char *p{buffer + n}; 34 *--p = '\0'; 35 while (hash > 1) { 36 if (p < buffer) { 37 return nullptr; 38 } 39 *--p = 'A' + (hash % 26); 40 hash /= 26; 41 } 42 return hash == 1 ? p : nullptr; 43 } 44 45 template <Direction DIR> 46 Cookie BeginInternalArrayListIO(const Descriptor &descriptor, 47 void ** /*scratchArea*/, std::size_t /*scratchBytes*/, 48 const char *sourceFile, int sourceLine) { 49 Terminator oom{sourceFile, sourceLine}; 50 return &New<InternalListIoStatementState<DIR>>{oom}( 51 descriptor, sourceFile, sourceLine) 52 .release() 53 ->ioStatementState(); 54 } 55 56 Cookie IONAME(BeginInternalArrayListOutput)(const Descriptor &descriptor, 57 void **scratchArea, std::size_t scratchBytes, const char *sourceFile, 58 int sourceLine) { 59 return BeginInternalArrayListIO<Direction::Output>( 60 descriptor, scratchArea, scratchBytes, sourceFile, sourceLine); 61 } 62 63 Cookie IONAME(BeginInternalArrayListInput)(const Descriptor &descriptor, 64 void **scratchArea, std::size_t scratchBytes, const char *sourceFile, 65 int sourceLine) { 66 return BeginInternalArrayListIO<Direction::Input>( 67 descriptor, scratchArea, scratchBytes, sourceFile, sourceLine); 68 } 69 70 template <Direction DIR> 71 Cookie BeginInternalArrayFormattedIO(const Descriptor &descriptor, 72 const char *format, std::size_t formatLength, void ** /*scratchArea*/, 73 std::size_t /*scratchBytes*/, const char *sourceFile, int sourceLine) { 74 Terminator oom{sourceFile, sourceLine}; 75 return &New<InternalFormattedIoStatementState<DIR>>{oom}( 76 descriptor, format, formatLength, sourceFile, sourceLine) 77 .release() 78 ->ioStatementState(); 79 } 80 81 Cookie IONAME(BeginInternalArrayFormattedOutput)(const Descriptor &descriptor, 82 const char *format, std::size_t formatLength, void **scratchArea, 83 std::size_t scratchBytes, const char *sourceFile, int sourceLine) { 84 return BeginInternalArrayFormattedIO<Direction::Output>(descriptor, format, 85 formatLength, scratchArea, scratchBytes, sourceFile, sourceLine); 86 } 87 88 Cookie IONAME(BeginInternalArrayFormattedInput)(const Descriptor &descriptor, 89 const char *format, std::size_t formatLength, void **scratchArea, 90 std::size_t scratchBytes, const char *sourceFile, int sourceLine) { 91 return BeginInternalArrayFormattedIO<Direction::Input>(descriptor, format, 92 formatLength, scratchArea, scratchBytes, sourceFile, sourceLine); 93 } 94 95 template <Direction DIR> 96 Cookie BeginInternalListIO( 97 std::conditional_t<DIR == Direction::Input, const char, char> *internal, 98 std::size_t internalLength, void ** /*scratchArea*/, 99 std::size_t /*scratchBytes*/, const char *sourceFile, int sourceLine) { 100 Terminator oom{sourceFile, sourceLine}; 101 return &New<InternalListIoStatementState<DIR>>{oom}( 102 internal, internalLength, sourceFile, sourceLine) 103 .release() 104 ->ioStatementState(); 105 } 106 107 Cookie IONAME(BeginInternalListOutput)(char *internal, 108 std::size_t internalLength, void **scratchArea, std::size_t scratchBytes, 109 const char *sourceFile, int sourceLine) { 110 return BeginInternalListIO<Direction::Output>(internal, internalLength, 111 scratchArea, scratchBytes, sourceFile, sourceLine); 112 } 113 114 Cookie IONAME(BeginInternalListInput)(const char *internal, 115 std::size_t internalLength, void **scratchArea, std::size_t scratchBytes, 116 const char *sourceFile, int sourceLine) { 117 return BeginInternalListIO<Direction::Input>(internal, internalLength, 118 scratchArea, scratchBytes, sourceFile, sourceLine); 119 } 120 121 template <Direction DIR> 122 Cookie BeginInternalFormattedIO( 123 std::conditional_t<DIR == Direction::Input, const char, char> *internal, 124 std::size_t internalLength, const char *format, std::size_t formatLength, 125 void ** /*scratchArea*/, std::size_t /*scratchBytes*/, 126 const char *sourceFile, int sourceLine) { 127 Terminator oom{sourceFile, sourceLine}; 128 return &New<InternalFormattedIoStatementState<DIR>>{oom}( 129 internal, internalLength, format, formatLength, sourceFile, sourceLine) 130 .release() 131 ->ioStatementState(); 132 } 133 134 Cookie IONAME(BeginInternalFormattedOutput)(char *internal, 135 std::size_t internalLength, const char *format, std::size_t formatLength, 136 void **scratchArea, std::size_t scratchBytes, const char *sourceFile, 137 int sourceLine) { 138 return BeginInternalFormattedIO<Direction::Output>(internal, internalLength, 139 format, formatLength, scratchArea, scratchBytes, sourceFile, sourceLine); 140 } 141 142 Cookie IONAME(BeginInternalFormattedInput)(const char *internal, 143 std::size_t internalLength, const char *format, std::size_t formatLength, 144 void **scratchArea, std::size_t scratchBytes, const char *sourceFile, 145 int sourceLine) { 146 return BeginInternalFormattedIO<Direction::Input>(internal, internalLength, 147 format, formatLength, scratchArea, scratchBytes, sourceFile, sourceLine); 148 } 149 150 template <Direction DIR> 151 Cookie BeginExternalListIO( 152 ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { 153 Terminator terminator{sourceFile, sourceLine}; 154 if (unitNumber == DefaultUnit) { 155 unitNumber = DIR == Direction::Input ? 5 : 6; 156 } 157 ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous( 158 unitNumber, DIR, false /*formatted*/, terminator)}; 159 if (unit.access == Access::Direct) { 160 terminator.Crash("List-directed I/O attempted on direct access file"); 161 return nullptr; 162 } 163 if (unit.isUnformatted) { 164 terminator.Crash("List-directed I/O attempted on unformatted file"); 165 return nullptr; 166 } 167 IoErrorHandler handler{terminator}; 168 unit.SetDirection(DIR, handler); 169 IoStatementState &io{unit.BeginIoStatement<ExternalListIoStatementState<DIR>>( 170 unit, sourceFile, sourceLine)}; 171 return &io; 172 } 173 174 Cookie IONAME(BeginExternalListOutput)( 175 ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { 176 return BeginExternalListIO<Direction::Output>( 177 unitNumber, sourceFile, sourceLine); 178 } 179 180 Cookie IONAME(BeginExternalListInput)( 181 ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { 182 return BeginExternalListIO<Direction::Input>( 183 unitNumber, sourceFile, sourceLine); 184 } 185 186 template <Direction DIR> 187 Cookie BeginExternalFormattedIO(const char *format, std::size_t formatLength, 188 ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { 189 Terminator terminator{sourceFile, sourceLine}; 190 if (unitNumber == DefaultUnit) { 191 unitNumber = DIR == Direction::Input ? 5 : 6; 192 } 193 ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous( 194 unitNumber, DIR, false /*formatted*/, terminator)}; 195 if (unit.isUnformatted) { 196 terminator.Crash("Formatted I/O attempted on unformatted file"); 197 return nullptr; 198 } 199 IoErrorHandler handler{terminator}; 200 unit.SetDirection(DIR, handler); 201 IoStatementState &io{ 202 unit.BeginIoStatement<ExternalFormattedIoStatementState<DIR>>( 203 unit, format, formatLength, sourceFile, sourceLine)}; 204 return &io; 205 } 206 207 Cookie IONAME(BeginExternalFormattedOutput)(const char *format, 208 std::size_t formatLength, ExternalUnit unitNumber, const char *sourceFile, 209 int sourceLine) { 210 return BeginExternalFormattedIO<Direction::Output>( 211 format, formatLength, unitNumber, sourceFile, sourceLine); 212 } 213 214 Cookie IONAME(BeginExternalFormattedInput)(const char *format, 215 std::size_t formatLength, ExternalUnit unitNumber, const char *sourceFile, 216 int sourceLine) { 217 return BeginExternalFormattedIO<Direction::Input>( 218 format, formatLength, unitNumber, sourceFile, sourceLine); 219 } 220 221 template <Direction DIR> 222 Cookie BeginUnformattedIO( 223 ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { 224 Terminator terminator{sourceFile, sourceLine}; 225 ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous( 226 unitNumber, DIR, true /*unformatted*/, terminator)}; 227 if (!unit.isUnformatted) { 228 terminator.Crash("Unformatted output attempted on formatted file"); 229 } 230 IoStatementState &io{unit.BeginIoStatement<UnformattedIoStatementState<DIR>>( 231 unit, sourceFile, sourceLine)}; 232 IoErrorHandler handler{terminator}; 233 unit.SetDirection(DIR, handler); 234 if constexpr (DIR == Direction::Output) { 235 if (unit.access == Access::Sequential && !unit.isFixedRecordLength) { 236 // Create space for (sub)record header to be completed by 237 // UnformattedIoStatementState<Direction::Output>::EndIoStatement() 238 unit.recordLength.reset(); // in case of prior BACKSPACE 239 io.Emit("\0\0\0\0", 4); // placeholder for record length header 240 } 241 } 242 return &io; 243 } 244 245 Cookie IONAME(BeginUnformattedOutput)( 246 ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { 247 return BeginUnformattedIO<Direction::Output>( 248 unitNumber, sourceFile, sourceLine); 249 } 250 251 Cookie IONAME(BeginUnformattedInput)( 252 ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { 253 return BeginUnformattedIO<Direction::Input>( 254 unitNumber, sourceFile, sourceLine); 255 } 256 257 Cookie IONAME(BeginOpenUnit)( // OPEN(without NEWUNIT=) 258 ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { 259 bool wasExtant{false}; 260 Terminator terminator{sourceFile, sourceLine}; 261 ExternalFileUnit &unit{ 262 ExternalFileUnit::LookUpOrCreate(unitNumber, terminator, wasExtant)}; 263 return &unit.BeginIoStatement<OpenStatementState>( 264 unit, wasExtant, sourceFile, sourceLine); 265 } 266 267 Cookie IONAME(BeginOpenNewUnit)( // OPEN(NEWUNIT=j) 268 const char *sourceFile, int sourceLine) { 269 Terminator terminator{sourceFile, sourceLine}; 270 bool ignored{false}; 271 ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreate( 272 ExternalFileUnit::NewUnit(terminator), terminator, ignored)}; 273 return &unit.BeginIoStatement<OpenStatementState>( 274 unit, false /*was an existing file*/, sourceFile, sourceLine); 275 } 276 277 Cookie IONAME(BeginClose)( 278 ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { 279 if (ExternalFileUnit * unit{ExternalFileUnit::LookUpForClose(unitNumber)}) { 280 return &unit->BeginIoStatement<CloseStatementState>( 281 *unit, sourceFile, sourceLine); 282 } else { 283 // CLOSE(UNIT=bad unit) is just a no-op 284 Terminator oom{sourceFile, sourceLine}; 285 return &New<NoopCloseStatementState>{oom}(sourceFile, sourceLine) 286 .release() 287 ->ioStatementState(); 288 } 289 } 290 291 Cookie IONAME(BeginFlush)( 292 ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { 293 Terminator terminator{sourceFile, sourceLine}; 294 ExternalFileUnit &unit{ 295 ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)}; 296 return &unit.BeginIoStatement<ExternalMiscIoStatementState>( 297 unit, ExternalMiscIoStatementState::Flush, sourceFile, sourceLine); 298 } 299 300 Cookie IONAME(BeginBackspace)( 301 ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { 302 Terminator terminator{sourceFile, sourceLine}; 303 ExternalFileUnit &unit{ 304 ExternalFileUnit::LookUpOrCrash(unitNumber, terminator)}; 305 return &unit.BeginIoStatement<ExternalMiscIoStatementState>( 306 unit, ExternalMiscIoStatementState::Backspace, sourceFile, sourceLine); 307 } 308 309 Cookie IONAME(BeginEndfile)( 310 ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { 311 Terminator terminator{sourceFile, sourceLine}; 312 ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous( 313 unitNumber, Direction::Output, true /*formatted*/, terminator)}; 314 return &unit.BeginIoStatement<ExternalMiscIoStatementState>( 315 unit, ExternalMiscIoStatementState::Endfile, sourceFile, sourceLine); 316 } 317 318 Cookie IONAME(BeginRewind)( 319 ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { 320 Terminator terminator{sourceFile, sourceLine}; 321 ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous( 322 unitNumber, Direction::Input, true /*formatted*/, terminator)}; 323 return &unit.BeginIoStatement<ExternalMiscIoStatementState>( 324 unit, ExternalMiscIoStatementState::Rewind, sourceFile, sourceLine); 325 } 326 327 Cookie IONAME(BeginInquireUnit)( 328 ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { 329 if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) { 330 return &unit->BeginIoStatement<InquireUnitState>( 331 *unit, sourceFile, sourceLine); 332 } else { 333 // INQUIRE(UNIT=unrecognized unit) 334 Terminator oom{sourceFile, sourceLine}; 335 return &New<InquireNoUnitState>{oom}(sourceFile, sourceLine) 336 .release() 337 ->ioStatementState(); 338 } 339 } 340 341 Cookie IONAME(BeginInquireFile)(const char *path, std::size_t pathLength, 342 const char *sourceFile, int sourceLine) { 343 Terminator oom{sourceFile, sourceLine}; 344 auto trimmed{ 345 SaveDefaultCharacter(path, TrimTrailingSpaces(path, pathLength), oom)}; 346 if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(trimmed.get())}) { 347 // INQUIRE(FILE=) to a connected unit 348 return &unit->BeginIoStatement<InquireUnitState>( 349 *unit, sourceFile, sourceLine); 350 } else { 351 return &New<InquireUnconnectedFileState>{oom}( 352 std::move(trimmed), sourceFile, sourceLine) 353 .release() 354 ->ioStatementState(); 355 } 356 } 357 358 Cookie IONAME(BeginInquireIoLength)(const char *sourceFile, int sourceLine) { 359 Terminator oom{sourceFile, sourceLine}; 360 return &New<InquireIOLengthState>{oom}(sourceFile, sourceLine) 361 .release() 362 ->ioStatementState(); 363 } 364 365 // Control list items 366 367 void IONAME(EnableHandlers)(Cookie cookie, bool hasIoStat, bool hasErr, 368 bool hasEnd, bool hasEor, bool hasIoMsg) { 369 IoErrorHandler &handler{cookie->GetIoErrorHandler()}; 370 if (hasIoStat) { 371 handler.HasIoStat(); 372 } 373 if (hasErr) { 374 handler.HasErrLabel(); 375 } 376 if (hasEnd) { 377 handler.HasEndLabel(); 378 } 379 if (hasEor) { 380 handler.HasEorLabel(); 381 } 382 if (hasIoMsg) { 383 handler.HasIoMsg(); 384 } 385 } 386 387 static bool YesOrNo(const char *keyword, std::size_t length, const char *what, 388 IoErrorHandler &handler) { 389 static const char *keywords[]{"YES", "NO", nullptr}; 390 switch (IdentifyValue(keyword, length, keywords)) { 391 case 0: 392 return true; 393 case 1: 394 return false; 395 default: 396 handler.SignalError(IostatErrorInKeyword, "Invalid %s='%.*s'", what, 397 static_cast<int>(length), keyword); 398 return false; 399 } 400 } 401 402 bool IONAME(SetAdvance)( 403 Cookie cookie, const char *keyword, std::size_t length) { 404 IoStatementState &io{*cookie}; 405 ConnectionState &connection{io.GetConnectionState()}; 406 connection.nonAdvancing = 407 !YesOrNo(keyword, length, "ADVANCE", io.GetIoErrorHandler()); 408 if (connection.nonAdvancing && connection.access == Access::Direct) { 409 io.GetIoErrorHandler().SignalError( 410 "Non-advancing I/O attempted on direct access file"); 411 } 412 return true; 413 } 414 415 bool IONAME(SetBlank)(Cookie cookie, const char *keyword, std::size_t length) { 416 IoStatementState &io{*cookie}; 417 ConnectionState &connection{io.GetConnectionState()}; 418 static const char *keywords[]{"NULL", "ZERO", nullptr}; 419 switch (IdentifyValue(keyword, length, keywords)) { 420 case 0: 421 connection.modes.editingFlags &= ~blankZero; 422 return true; 423 case 1: 424 connection.modes.editingFlags |= blankZero; 425 return true; 426 default: 427 io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, 428 "Invalid BLANK='%.*s'", static_cast<int>(length), keyword); 429 return false; 430 } 431 } 432 433 bool IONAME(SetDecimal)( 434 Cookie cookie, const char *keyword, std::size_t length) { 435 IoStatementState &io{*cookie}; 436 ConnectionState &connection{io.GetConnectionState()}; 437 static const char *keywords[]{"COMMA", "POINT", nullptr}; 438 switch (IdentifyValue(keyword, length, keywords)) { 439 case 0: 440 connection.modes.editingFlags |= decimalComma; 441 return true; 442 case 1: 443 connection.modes.editingFlags &= ~decimalComma; 444 return true; 445 default: 446 io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, 447 "Invalid DECIMAL='%.*s'", static_cast<int>(length), keyword); 448 return false; 449 } 450 } 451 452 bool IONAME(SetDelim)(Cookie cookie, const char *keyword, std::size_t length) { 453 IoStatementState &io{*cookie}; 454 ConnectionState &connection{io.GetConnectionState()}; 455 static const char *keywords[]{"APOSTROPHE", "QUOTE", "NONE", nullptr}; 456 switch (IdentifyValue(keyword, length, keywords)) { 457 case 0: 458 connection.modes.delim = '\''; 459 return true; 460 case 1: 461 connection.modes.delim = '"'; 462 return true; 463 case 2: 464 connection.modes.delim = '\0'; 465 return true; 466 default: 467 io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, 468 "Invalid DELIM='%.*s'", static_cast<int>(length), keyword); 469 return false; 470 } 471 } 472 473 bool IONAME(SetPad)(Cookie cookie, const char *keyword, std::size_t length) { 474 IoStatementState &io{*cookie}; 475 ConnectionState &connection{io.GetConnectionState()}; 476 connection.modes.pad = 477 YesOrNo(keyword, length, "PAD", io.GetIoErrorHandler()); 478 return true; 479 } 480 481 bool IONAME(SetPos)(Cookie cookie, std::int64_t pos) { 482 IoStatementState &io{*cookie}; 483 ConnectionState &connection{io.GetConnectionState()}; 484 if (connection.access != Access::Stream) { 485 io.GetIoErrorHandler().SignalError( 486 "REC= may not appear unless ACCESS='STREAM'"); 487 return false; 488 } 489 if (pos < 1) { 490 io.GetIoErrorHandler().SignalError( 491 "POS=%zd is invalid", static_cast<std::intmax_t>(pos)); 492 return false; 493 } 494 if (auto *unit{io.GetExternalFileUnit()}) { 495 unit->SetPosition(pos); 496 return true; 497 } 498 io.GetIoErrorHandler().Crash("SetPos() on internal unit"); 499 return false; 500 } 501 502 bool IONAME(SetRec)(Cookie cookie, std::int64_t rec) { 503 IoStatementState &io{*cookie}; 504 ConnectionState &connection{io.GetConnectionState()}; 505 if (connection.access != Access::Direct) { 506 io.GetIoErrorHandler().SignalError( 507 "REC= may not appear unless ACCESS='DIRECT'"); 508 return false; 509 } 510 if (!connection.isFixedRecordLength || !connection.recordLength) { 511 io.GetIoErrorHandler().SignalError("RECL= was not specified"); 512 return false; 513 } 514 if (rec < 1) { 515 io.GetIoErrorHandler().SignalError( 516 "REC=%zd is invalid", static_cast<std::intmax_t>(rec)); 517 return false; 518 } 519 connection.currentRecordNumber = rec; 520 if (auto *unit{io.GetExternalFileUnit()}) { 521 unit->SetPosition((rec - 1) * *connection.recordLength); 522 } 523 return true; 524 } 525 526 bool IONAME(SetRound)(Cookie cookie, const char *keyword, std::size_t length) { 527 IoStatementState &io{*cookie}; 528 ConnectionState &connection{io.GetConnectionState()}; 529 static const char *keywords[]{"UP", "DOWN", "ZERO", "NEAREST", "COMPATIBLE", 530 "PROCESSOR_DEFINED", nullptr}; 531 switch (IdentifyValue(keyword, length, keywords)) { 532 case 0: 533 connection.modes.round = decimal::RoundUp; 534 return true; 535 case 1: 536 connection.modes.round = decimal::RoundDown; 537 return true; 538 case 2: 539 connection.modes.round = decimal::RoundToZero; 540 return true; 541 case 3: 542 connection.modes.round = decimal::RoundNearest; 543 return true; 544 case 4: 545 connection.modes.round = decimal::RoundCompatible; 546 return true; 547 case 5: 548 connection.modes.round = executionEnvironment.defaultOutputRoundingMode; 549 return true; 550 default: 551 io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, 552 "Invalid ROUND='%.*s'", static_cast<int>(length), keyword); 553 return false; 554 } 555 } 556 557 bool IONAME(SetSign)(Cookie cookie, const char *keyword, std::size_t length) { 558 IoStatementState &io{*cookie}; 559 ConnectionState &connection{io.GetConnectionState()}; 560 static const char *keywords[]{"PLUS", "YES", "PROCESSOR_DEFINED", nullptr}; 561 switch (IdentifyValue(keyword, length, keywords)) { 562 case 0: 563 connection.modes.editingFlags |= signPlus; 564 return true; 565 case 1: 566 case 2: // processor default is SS 567 connection.modes.editingFlags &= ~signPlus; 568 return true; 569 default: 570 io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, 571 "Invalid SIGN='%.*s'", static_cast<int>(length), keyword); 572 return false; 573 } 574 } 575 576 bool IONAME(SetAccess)(Cookie cookie, const char *keyword, std::size_t length) { 577 IoStatementState &io{*cookie}; 578 auto *open{io.get_if<OpenStatementState>()}; 579 if (!open) { 580 io.GetIoErrorHandler().Crash( 581 "SetAccess() called when not in an OPEN statement"); 582 } 583 static const char *keywords[]{"SEQUENTIAL", "DIRECT", "STREAM", nullptr}; 584 switch (IdentifyValue(keyword, length, keywords)) { 585 case 0: 586 open->set_access(Access::Sequential); 587 break; 588 case 1: 589 open->set_access(Access::Direct); 590 break; 591 case 2: 592 open->set_access(Access::Stream); 593 break; 594 default: 595 open->SignalError(IostatErrorInKeyword, "Invalid ACCESS='%.*s'", 596 static_cast<int>(length), keyword); 597 } 598 return true; 599 } 600 601 bool IONAME(SetAction)(Cookie cookie, const char *keyword, std::size_t length) { 602 IoStatementState &io{*cookie}; 603 auto *open{io.get_if<OpenStatementState>()}; 604 if (!open) { 605 io.GetIoErrorHandler().Crash( 606 "SetAction() called when not in an OPEN statement"); 607 } 608 std::optional<Action> action; 609 static const char *keywords[]{"READ", "WRITE", "READWRITE", nullptr}; 610 switch (IdentifyValue(keyword, length, keywords)) { 611 case 0: 612 action = Action::Read; 613 break; 614 case 1: 615 action = Action::Write; 616 break; 617 case 2: 618 action = Action::ReadWrite; 619 break; 620 default: 621 open->SignalError(IostatErrorInKeyword, "Invalid ACTION='%.*s'", 622 static_cast<int>(length), keyword); 623 return false; 624 } 625 RUNTIME_CHECK(io.GetIoErrorHandler(), action.has_value()); 626 if (open->wasExtant()) { 627 if ((*action != Action::Write) != open->unit().mayRead() || 628 (*action != Action::Read) != open->unit().mayWrite()) { 629 open->SignalError("ACTION= may not be changed on an open unit"); 630 } 631 } 632 open->set_action(*action); 633 return true; 634 } 635 636 bool IONAME(SetAsynchronous)( 637 Cookie cookie, const char *keyword, std::size_t length) { 638 IoStatementState &io{*cookie}; 639 auto *open{io.get_if<OpenStatementState>()}; 640 if (!open) { 641 io.GetIoErrorHandler().Crash( 642 "SetAsynchronous() called when not in an OPEN statement"); 643 } 644 static const char *keywords[]{"YES", "NO", nullptr}; 645 switch (IdentifyValue(keyword, length, keywords)) { 646 case 0: 647 open->unit().set_mayAsynchronous(true); 648 return true; 649 case 1: 650 open->unit().set_mayAsynchronous(false); 651 return true; 652 default: 653 open->SignalError(IostatErrorInKeyword, "Invalid ASYNCHRONOUS='%.*s'", 654 static_cast<int>(length), keyword); 655 return false; 656 } 657 } 658 659 bool IONAME(SetCarriagecontrol)( 660 Cookie cookie, const char *keyword, std::size_t length) { 661 IoStatementState &io{*cookie}; 662 auto *open{io.get_if<OpenStatementState>()}; 663 if (!open) { 664 io.GetIoErrorHandler().Crash( 665 "SetCarriageControl() called when not in an OPEN statement"); 666 } 667 static const char *keywords[]{"LIST", "FORTRAN", "NONE", nullptr}; 668 switch (IdentifyValue(keyword, length, keywords)) { 669 case 0: 670 return true; 671 case 1: 672 case 2: 673 open->SignalError(IostatErrorInKeyword, 674 "Unimplemented CARRIAGECONTROL='%.*s'", static_cast<int>(length), 675 keyword); 676 return false; 677 default: 678 open->SignalError(IostatErrorInKeyword, "Invalid CARRIAGECONTROL='%.*s'", 679 static_cast<int>(length), keyword); 680 return false; 681 } 682 } 683 684 bool IONAME(SetConvert)( 685 Cookie cookie, const char *keyword, std::size_t length) { 686 IoStatementState &io{*cookie}; 687 auto *open{io.get_if<OpenStatementState>()}; 688 if (!open) { 689 io.GetIoErrorHandler().Crash( 690 "SetConvert() called when not in an OPEN statement"); 691 } 692 if (auto convert{GetConvertFromString(keyword, length)}) { 693 open->set_convert(*convert); 694 return true; 695 } else { 696 open->SignalError(IostatErrorInKeyword, "Invalid CONVERT='%.*s'", 697 static_cast<int>(length), keyword); 698 return false; 699 } 700 } 701 702 bool IONAME(SetEncoding)( 703 Cookie cookie, const char *keyword, std::size_t length) { 704 IoStatementState &io{*cookie}; 705 auto *open{io.get_if<OpenStatementState>()}; 706 if (!open) { 707 io.GetIoErrorHandler().Crash( 708 "SetEncoding() called when not in an OPEN statement"); 709 } 710 bool isUTF8{false}; 711 static const char *keywords[]{"UTF-8", "DEFAULT", nullptr}; 712 switch (IdentifyValue(keyword, length, keywords)) { 713 case 0: 714 isUTF8 = true; 715 break; 716 case 1: 717 isUTF8 = false; 718 break; 719 default: 720 open->SignalError(IostatErrorInKeyword, "Invalid ENCODING='%.*s'", 721 static_cast<int>(length), keyword); 722 } 723 if (isUTF8 != open->unit().isUTF8) { 724 if (open->wasExtant()) { 725 open->SignalError("ENCODING= may not be changed on an open unit"); 726 } 727 open->unit().isUTF8 = isUTF8; 728 } 729 return true; 730 } 731 732 bool IONAME(SetForm)(Cookie cookie, const char *keyword, std::size_t length) { 733 IoStatementState &io{*cookie}; 734 auto *open{io.get_if<OpenStatementState>()}; 735 if (!open) { 736 io.GetIoErrorHandler().Crash( 737 "SetForm() called when not in an OPEN statement"); 738 } 739 static const char *keywords[]{"FORMATTED", "UNFORMATTED", nullptr}; 740 switch (IdentifyValue(keyword, length, keywords)) { 741 case 0: 742 open->set_isUnformatted(false); 743 break; 744 case 1: 745 open->set_isUnformatted(true); 746 break; 747 default: 748 open->SignalError(IostatErrorInKeyword, "Invalid FORM='%.*s'", 749 static_cast<int>(length), keyword); 750 } 751 return true; 752 } 753 754 bool IONAME(SetPosition)( 755 Cookie cookie, const char *keyword, std::size_t length) { 756 IoStatementState &io{*cookie}; 757 auto *open{io.get_if<OpenStatementState>()}; 758 if (!open) { 759 io.GetIoErrorHandler().Crash( 760 "SetPosition() called when not in an OPEN statement"); 761 } 762 static const char *positions[]{"ASIS", "REWIND", "APPEND", nullptr}; 763 switch (IdentifyValue(keyword, length, positions)) { 764 case 0: 765 open->set_position(Position::AsIs); 766 return true; 767 case 1: 768 open->set_position(Position::Rewind); 769 return true; 770 case 2: 771 open->set_position(Position::Append); 772 return true; 773 default: 774 io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, 775 "Invalid POSITION='%.*s'", static_cast<int>(length), keyword); 776 } 777 return true; 778 } 779 780 bool IONAME(SetRecl)(Cookie cookie, std::size_t n) { 781 IoStatementState &io{*cookie}; 782 auto *open{io.get_if<OpenStatementState>()}; 783 if (!open) { 784 io.GetIoErrorHandler().Crash( 785 "SetRecl() called when not in an OPEN statement"); 786 } 787 if (n <= 0) { 788 io.GetIoErrorHandler().SignalError("RECL= must be greater than zero"); 789 } 790 if (open->wasExtant() && open->unit().isFixedRecordLength && 791 open->unit().recordLength.value_or(n) != static_cast<std::int64_t>(n)) { 792 open->SignalError("RECL= may not be changed for an open unit"); 793 } 794 open->unit().isFixedRecordLength = true; 795 open->unit().recordLength = n; 796 return true; 797 } 798 799 bool IONAME(SetStatus)(Cookie cookie, const char *keyword, std::size_t length) { 800 IoStatementState &io{*cookie}; 801 if (auto *open{io.get_if<OpenStatementState>()}) { 802 static const char *statuses[]{ 803 "OLD", "NEW", "SCRATCH", "REPLACE", "UNKNOWN", nullptr}; 804 switch (IdentifyValue(keyword, length, statuses)) { 805 case 0: 806 open->set_status(OpenStatus::Old); 807 return true; 808 case 1: 809 open->set_status(OpenStatus::New); 810 return true; 811 case 2: 812 open->set_status(OpenStatus::Scratch); 813 return true; 814 case 3: 815 open->set_status(OpenStatus::Replace); 816 return true; 817 case 4: 818 open->set_status(OpenStatus::Unknown); 819 return true; 820 default: 821 io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, 822 "Invalid STATUS='%.*s'", static_cast<int>(length), keyword); 823 } 824 return false; 825 } 826 if (auto *close{io.get_if<CloseStatementState>()}) { 827 static const char *statuses[]{"KEEP", "DELETE", nullptr}; 828 switch (IdentifyValue(keyword, length, statuses)) { 829 case 0: 830 close->set_status(CloseStatus::Keep); 831 return true; 832 case 1: 833 close->set_status(CloseStatus::Delete); 834 return true; 835 default: 836 io.GetIoErrorHandler().SignalError(IostatErrorInKeyword, 837 "Invalid STATUS='%.*s'", static_cast<int>(length), keyword); 838 } 839 return false; 840 } 841 if (io.get_if<NoopCloseStatementState>()) { 842 return true; // don't bother validating STATUS= in a no-op CLOSE 843 } 844 io.GetIoErrorHandler().Crash( 845 "SetStatus() called when not in an OPEN or CLOSE statement"); 846 } 847 848 bool IONAME(SetFile)(Cookie cookie, const char *path, std::size_t chars) { 849 IoStatementState &io{*cookie}; 850 if (auto *open{io.get_if<OpenStatementState>()}) { 851 open->set_path(path, chars); 852 return true; 853 } 854 io.GetIoErrorHandler().Crash( 855 "SetFile() called when not in an OPEN statement"); 856 return false; 857 } 858 859 template <typename INT> 860 static bool SetInteger(INT &x, int kind, std::int64_t value) { 861 switch (kind) { 862 case 1: 863 reinterpret_cast<std::int8_t &>(x) = value; 864 return true; 865 case 2: 866 reinterpret_cast<std::int16_t &>(x) = value; 867 return true; 868 case 4: 869 reinterpret_cast<std::int32_t &>(x) = value; 870 return true; 871 case 8: 872 reinterpret_cast<std::int64_t &>(x) = value; 873 return true; 874 default: 875 return false; 876 } 877 } 878 879 bool IONAME(GetNewUnit)(Cookie cookie, int &unit, int kind) { 880 IoStatementState &io{*cookie}; 881 auto *open{io.get_if<OpenStatementState>()}; 882 if (!open) { 883 io.GetIoErrorHandler().Crash( 884 "GetNewUnit() called when not in an OPEN statement"); 885 } 886 if (!SetInteger(unit, kind, open->unit().unitNumber())) { 887 open->SignalError("GetNewUnit(): Bad INTEGER kind(%d) for result"); 888 } 889 return true; 890 } 891 892 // Data transfers 893 894 bool IONAME(OutputDescriptor)(Cookie cookie, const Descriptor &descriptor) { 895 return descr::DescriptorIO<Direction::Output>(*cookie, descriptor); 896 } 897 898 bool IONAME(InputDescriptor)(Cookie cookie, const Descriptor &descriptor) { 899 return descr::DescriptorIO<Direction::Input>(*cookie, descriptor); 900 } 901 902 bool IONAME(OutputUnformattedBlock)(Cookie cookie, const char *x, 903 std::size_t length, std::size_t elementBytes) { 904 IoStatementState &io{*cookie}; 905 if (auto *unf{io.get_if<UnformattedIoStatementState<Direction::Output>>()}) { 906 return unf->Emit(x, length, elementBytes); 907 } 908 io.GetIoErrorHandler().Crash("OutputUnformattedBlock() called for an I/O " 909 "statement that is not unformatted output"); 910 return false; 911 } 912 913 bool IONAME(InputUnformattedBlock)( 914 Cookie cookie, char *x, std::size_t length, std::size_t elementBytes) { 915 IoStatementState &io{*cookie}; 916 io.BeginReadingRecord(); 917 if (auto *unf{io.get_if<UnformattedIoStatementState<Direction::Input>>()}) { 918 return unf->Receive(x, length, elementBytes); 919 } 920 io.GetIoErrorHandler().Crash("InputUnformattedBlock() called for an I/O " 921 "statement that is not unformatted output"); 922 return false; 923 } 924 925 bool IONAME(OutputInteger64)(Cookie cookie, std::int64_t n) { 926 cookie->CheckFormattedStmtType<Direction::Output>("OutputInteger64"); 927 StaticDescriptor staticDescriptor; 928 Descriptor &descriptor{staticDescriptor.descriptor()}; 929 descriptor.Establish( 930 TypeCategory::Integer, sizeof n, reinterpret_cast<void *>(&n), 0); 931 return descr::DescriptorIO<Direction::Output>(*cookie, descriptor); 932 } 933 934 bool IONAME(InputInteger)(Cookie cookie, std::int64_t &n, int kind) { 935 cookie->CheckFormattedStmtType<Direction::Input>("InputInteger"); 936 StaticDescriptor staticDescriptor; 937 Descriptor &descriptor{staticDescriptor.descriptor()}; 938 descriptor.Establish( 939 TypeCategory::Integer, kind, reinterpret_cast<void *>(&n), 0); 940 return descr::DescriptorIO<Direction::Input>(*cookie, descriptor); 941 } 942 943 bool IONAME(OutputReal32)(Cookie cookie, float x) { 944 cookie->CheckFormattedStmtType<Direction::Output>("OutputReal32"); 945 StaticDescriptor staticDescriptor; 946 Descriptor &descriptor{staticDescriptor.descriptor()}; 947 descriptor.Establish(TypeCategory::Real, 4, reinterpret_cast<void *>(&x), 0); 948 return descr::DescriptorIO<Direction::Output>(*cookie, descriptor); 949 } 950 951 bool IONAME(OutputReal64)(Cookie cookie, double x) { 952 cookie->CheckFormattedStmtType<Direction::Output>("OutputReal64"); 953 StaticDescriptor staticDescriptor; 954 Descriptor &descriptor{staticDescriptor.descriptor()}; 955 descriptor.Establish(TypeCategory::Real, 8, reinterpret_cast<void *>(&x), 0); 956 return descr::DescriptorIO<Direction::Output>(*cookie, descriptor); 957 } 958 959 bool IONAME(InputReal32)(Cookie cookie, float &x) { 960 cookie->CheckFormattedStmtType<Direction::Input>("InputReal32"); 961 StaticDescriptor staticDescriptor; 962 Descriptor &descriptor{staticDescriptor.descriptor()}; 963 descriptor.Establish(TypeCategory::Real, 4, reinterpret_cast<void *>(&x), 0); 964 return descr::DescriptorIO<Direction::Input>(*cookie, descriptor); 965 } 966 967 bool IONAME(InputReal64)(Cookie cookie, double &x) { 968 cookie->CheckFormattedStmtType<Direction::Input>("InputReal64"); 969 StaticDescriptor staticDescriptor; 970 Descriptor &descriptor{staticDescriptor.descriptor()}; 971 descriptor.Establish(TypeCategory::Real, 8, reinterpret_cast<void *>(&x), 0); 972 return descr::DescriptorIO<Direction::Input>(*cookie, descriptor); 973 } 974 975 bool IONAME(OutputComplex32)(Cookie cookie, float r, float i) { 976 cookie->CheckFormattedStmtType<Direction::Output>("OutputComplex32"); 977 float z[2]{r, i}; 978 StaticDescriptor staticDescriptor; 979 Descriptor &descriptor{staticDescriptor.descriptor()}; 980 descriptor.Establish( 981 TypeCategory::Complex, 4, reinterpret_cast<void *>(&z), 0); 982 return descr::DescriptorIO<Direction::Output>(*cookie, descriptor); 983 } 984 985 bool IONAME(OutputComplex64)(Cookie cookie, double r, double i) { 986 cookie->CheckFormattedStmtType<Direction::Output>("OutputComplex64"); 987 double z[2]{r, i}; 988 StaticDescriptor staticDescriptor; 989 Descriptor &descriptor{staticDescriptor.descriptor()}; 990 descriptor.Establish( 991 TypeCategory::Complex, 8, reinterpret_cast<void *>(&z), 0); 992 return descr::DescriptorIO<Direction::Output>(*cookie, descriptor); 993 } 994 995 bool IONAME(InputComplex32)(Cookie cookie, float z[2]) { 996 cookie->CheckFormattedStmtType<Direction::Input>("InputComplex32"); 997 StaticDescriptor staticDescriptor; 998 Descriptor &descriptor{staticDescriptor.descriptor()}; 999 descriptor.Establish( 1000 TypeCategory::Complex, 4, reinterpret_cast<void *>(z), 0); 1001 return descr::DescriptorIO<Direction::Input>(*cookie, descriptor); 1002 } 1003 1004 bool IONAME(InputComplex64)(Cookie cookie, double z[2]) { 1005 cookie->CheckFormattedStmtType<Direction::Input>("InputComplex64"); 1006 StaticDescriptor staticDescriptor; 1007 Descriptor &descriptor{staticDescriptor.descriptor()}; 1008 descriptor.Establish( 1009 TypeCategory::Complex, 8, reinterpret_cast<void *>(z), 0); 1010 return descr::DescriptorIO<Direction::Input>(*cookie, descriptor); 1011 } 1012 1013 bool IONAME(OutputCharacter)( 1014 Cookie cookie, const char *x, std::size_t length, int kind) { 1015 cookie->CheckFormattedStmtType<Direction::Output>("OutputCharacter"); 1016 StaticDescriptor staticDescriptor; 1017 Descriptor &descriptor{staticDescriptor.descriptor()}; 1018 descriptor.Establish( 1019 kind, length, reinterpret_cast<void *>(const_cast<char *>(x)), 0); 1020 return descr::DescriptorIO<Direction::Output>(*cookie, descriptor); 1021 } 1022 1023 bool IONAME(OutputAscii)(Cookie cookie, const char *x, std::size_t length) { 1024 return IONAME(OutputCharacter(cookie, x, length, 1)); 1025 } 1026 1027 bool IONAME(InputCharacter)( 1028 Cookie cookie, char *x, std::size_t length, int kind) { 1029 cookie->CheckFormattedStmtType<Direction::Input>("InputCharacter"); 1030 StaticDescriptor staticDescriptor; 1031 Descriptor &descriptor{staticDescriptor.descriptor()}; 1032 descriptor.Establish(kind, length, reinterpret_cast<void *>(x), 0); 1033 return descr::DescriptorIO<Direction::Input>(*cookie, descriptor); 1034 } 1035 1036 bool IONAME(InputAscii)(Cookie cookie, char *x, std::size_t length) { 1037 return IONAME(InputCharacter(cookie, x, length, 1)); 1038 } 1039 1040 bool IONAME(OutputLogical)(Cookie cookie, bool truth) { 1041 cookie->CheckFormattedStmtType<Direction::Output>("OutputLogical"); 1042 StaticDescriptor staticDescriptor; 1043 Descriptor &descriptor{staticDescriptor.descriptor()}; 1044 descriptor.Establish( 1045 TypeCategory::Logical, sizeof truth, reinterpret_cast<void *>(&truth), 0); 1046 return descr::DescriptorIO<Direction::Output>(*cookie, descriptor); 1047 } 1048 1049 bool IONAME(InputLogical)(Cookie cookie, bool &truth) { 1050 cookie->CheckFormattedStmtType<Direction::Input>("InputLogical"); 1051 StaticDescriptor staticDescriptor; 1052 Descriptor &descriptor{staticDescriptor.descriptor()}; 1053 descriptor.Establish( 1054 TypeCategory::Logical, sizeof truth, reinterpret_cast<void *>(&truth), 0); 1055 return descr::DescriptorIO<Direction::Input>(*cookie, descriptor); 1056 } 1057 1058 void IONAME(GetIoMsg)(Cookie cookie, char *msg, std::size_t length) { 1059 IoErrorHandler &handler{cookie->GetIoErrorHandler()}; 1060 if (handler.GetIoStat()) { // leave "msg" alone when no error 1061 handler.GetIoMsg(msg, length); 1062 } 1063 } 1064 1065 bool IONAME(InquireCharacter)(Cookie cookie, InquiryKeywordHash inquiry, 1066 char *result, std::size_t length) { 1067 IoStatementState &io{*cookie}; 1068 return io.Inquire(inquiry, result, length); 1069 } 1070 1071 bool IONAME(InquireLogical)( 1072 Cookie cookie, InquiryKeywordHash inquiry, bool &result) { 1073 IoStatementState &io{*cookie}; 1074 return io.Inquire(inquiry, result); 1075 } 1076 1077 bool IONAME(InquirePendingId)(Cookie cookie, std::int64_t id, bool &result) { 1078 IoStatementState &io{*cookie}; 1079 return io.Inquire(HashInquiryKeyword("PENDING"), id, result); 1080 } 1081 1082 bool IONAME(InquireInteger64)( 1083 Cookie cookie, InquiryKeywordHash inquiry, std::int64_t &result, int kind) { 1084 IoStatementState &io{*cookie}; 1085 std::int64_t n; 1086 if (io.Inquire(inquiry, n)) { 1087 SetInteger(result, kind, n); 1088 return true; 1089 } 1090 return false; 1091 } 1092 1093 enum Iostat IONAME(EndIoStatement)(Cookie cookie) { 1094 IoStatementState &io{*cookie}; 1095 return static_cast<enum Iostat>(io.EndIoStatement()); 1096 } 1097 } // namespace Fortran::runtime::io 1098