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