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