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