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