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