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