1 //===-- runtime/edit-input.cpp --------------------------------------------===// 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 #include "edit-input.h" 10 #include "namelist.h" 11 #include "utf.h" 12 #include "flang/Common/real.h" 13 #include "flang/Common/uint128.h" 14 #include <algorithm> 15 #include <cfenv> 16 17 namespace Fortran::runtime::io { 18 19 static bool EditBOZInput(IoStatementState &io, const DataEdit &edit, void *n, 20 int base, int totalBitSize) { 21 std::optional<int> remaining; 22 std::optional<char32_t> next{io.PrepareInput(edit, remaining)}; 23 common::UnsignedInt128 value{0}; 24 for (; next; next = io.NextInField(remaining, edit)) { 25 char32_t ch{*next}; 26 if (ch == ' ' || ch == '\t') { 27 continue; 28 } 29 int digit{0}; 30 if (ch >= '0' && ch <= '1') { 31 digit = ch - '0'; 32 } else if (base >= 8 && ch >= '2' && ch <= '7') { 33 digit = ch - '0'; 34 } else if (base >= 10 && ch >= '8' && ch <= '9') { 35 digit = ch - '0'; 36 } else if (base == 16 && ch >= 'A' && ch <= 'Z') { 37 digit = ch + 10 - 'A'; 38 } else if (base == 16 && ch >= 'a' && ch <= 'z') { 39 digit = ch + 10 - 'a'; 40 } else { 41 io.GetIoErrorHandler().SignalError( 42 "Bad character '%lc' in B/O/Z input field", ch); 43 return false; 44 } 45 value *= base; 46 value += digit; 47 } 48 // TODO: check for overflow 49 std::memcpy(n, &value, totalBitSize >> 3); 50 return true; 51 } 52 53 static inline char32_t GetDecimalPoint(const DataEdit &edit) { 54 return edit.modes.editingFlags & decimalComma ? char32_t{','} : char32_t{'.'}; 55 } 56 57 // Prepares input from a field, and consumes the sign, if any. 58 // Returns true if there's a '-' sign. 59 static bool ScanNumericPrefix(IoStatementState &io, const DataEdit &edit, 60 std::optional<char32_t> &next, std::optional<int> &remaining) { 61 next = io.PrepareInput(edit, remaining); 62 bool negative{false}; 63 if (next) { 64 negative = *next == '-'; 65 if (negative || *next == '+') { 66 io.SkipSpaces(remaining); 67 next = io.NextInField(remaining, edit); 68 } 69 } 70 return negative; 71 } 72 73 bool EditIntegerInput( 74 IoStatementState &io, const DataEdit &edit, void *n, int kind) { 75 RUNTIME_CHECK(io.GetIoErrorHandler(), kind >= 1 && !(kind & (kind - 1))); 76 switch (edit.descriptor) { 77 case DataEdit::ListDirected: 78 if (IsNamelistName(io)) { 79 return false; 80 } 81 break; 82 case 'G': 83 case 'I': 84 break; 85 case 'B': 86 return EditBOZInput(io, edit, n, 2, kind << 3); 87 case 'O': 88 return EditBOZInput(io, edit, n, 8, kind << 3); 89 case 'Z': 90 return EditBOZInput(io, edit, n, 16, kind << 3); 91 case 'A': // legacy extension 92 return EditCharacterInput(io, edit, reinterpret_cast<char *>(n), kind); 93 default: 94 io.GetIoErrorHandler().SignalError(IostatErrorInFormat, 95 "Data edit descriptor '%c' may not be used with an INTEGER data item", 96 edit.descriptor); 97 return false; 98 } 99 std::optional<int> remaining; 100 std::optional<char32_t> next; 101 bool negate{ScanNumericPrefix(io, edit, next, remaining)}; 102 common::UnsignedInt128 value{0}; 103 bool any{negate}; 104 for (; next; next = io.NextInField(remaining, edit)) { 105 char32_t ch{*next}; 106 if (ch == ' ' || ch == '\t') { 107 if (edit.modes.editingFlags & blankZero) { 108 ch = '0'; // BZ mode - treat blank as if it were zero 109 } else { 110 continue; 111 } 112 } 113 int digit{0}; 114 if (ch >= '0' && ch <= '9') { 115 digit = ch - '0'; 116 } else { 117 io.GetIoErrorHandler().SignalError( 118 "Bad character '%lc' in INTEGER input field", ch); 119 return false; 120 } 121 value *= 10; 122 value += digit; 123 any = true; 124 } 125 if (negate) { 126 value = -value; 127 } 128 if (any || !io.GetConnectionState().IsAtEOF()) { 129 std::memcpy(n, &value, kind); // a blank field means zero 130 } 131 return any; 132 } 133 134 // Parses a REAL input number from the input source as a normalized 135 // fraction into a supplied buffer -- there's an optional '-', a 136 // decimal point, and at least one digit. The adjusted exponent value 137 // is returned in a reference argument. The returned value is the number 138 // of characters that (should) have been written to the buffer -- this can 139 // be larger than the buffer size and can indicate overflow. Replaces 140 // blanks with zeroes if appropriate. 141 static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io, 142 const DataEdit &edit, int &exponent) { 143 std::optional<int> remaining; 144 std::optional<char32_t> next; 145 int got{0}; 146 std::optional<int> decimalPoint; 147 auto Put{[&](char ch) -> void { 148 if (got < bufferSize) { 149 buffer[got] = ch; 150 } 151 ++got; 152 }}; 153 if (ScanNumericPrefix(io, edit, next, remaining)) { 154 Put('-'); 155 } 156 bool bzMode{(edit.modes.editingFlags & blankZero) != 0}; 157 if (!next || (!bzMode && *next == ' ')) { // empty/blank field means zero 158 remaining.reset(); 159 if (!io.GetConnectionState().IsAtEOF()) { 160 Put('0'); 161 } 162 return got; 163 } 164 char32_t decimal{GetDecimalPoint(edit)}; 165 char32_t first{*next >= 'a' && *next <= 'z' ? *next + 'A' - 'a' : *next}; 166 if (first == 'N' || first == 'I') { 167 // NaN or infinity - convert to upper case 168 // Subtle: a blank field of digits could be followed by 'E' or 'D', 169 for (; next && 170 ((*next >= 'a' && *next <= 'z') || (*next >= 'A' && *next <= 'Z')); 171 next = io.NextInField(remaining, edit)) { 172 if (*next >= 'a' && *next <= 'z') { 173 Put(*next - 'a' + 'A'); 174 } else { 175 Put(*next); 176 } 177 } 178 if (next && *next == '(') { // NaN(...) 179 Put('('); 180 int depth{1}; 181 do { 182 next = io.NextInField(remaining, edit); 183 if (!next) { 184 break; 185 } else if (*next == '(') { 186 ++depth; 187 } else if (*next == ')') { 188 --depth; 189 } 190 Put(*next); 191 } while (depth > 0); 192 } 193 exponent = 0; 194 } else if (first == decimal || (first >= '0' && first <= '9') || 195 (bzMode && (first == ' ' || first == '\t')) || first == 'E' || 196 first == 'D' || first == 'Q') { 197 Put('.'); // input field is normalized to a fraction 198 auto start{got}; 199 for (; next; next = io.NextInField(remaining, edit)) { 200 char32_t ch{*next}; 201 if (ch == ' ' || ch == '\t') { 202 if (bzMode) { 203 ch = '0'; // BZ mode - treat blank as if it were zero 204 } else { 205 continue; 206 } 207 } 208 if (ch == '0' && got == start && !decimalPoint) { 209 // omit leading zeroes before the decimal 210 } else if (ch >= '0' && ch <= '9') { 211 Put(ch); 212 } else if (ch == decimal && !decimalPoint) { 213 // the decimal point is *not* copied to the buffer 214 decimalPoint = got - start; // # of digits before the decimal point 215 } else { 216 break; 217 } 218 } 219 if (got == start) { 220 // Nothing but zeroes and maybe a decimal point. F'2018 requires 221 // at least one digit, but F'77 did not, and a bare "." shows up in 222 // the FCVS suite. 223 Put('0'); // emit at least one digit 224 } 225 if (next && 226 (*next == 'e' || *next == 'E' || *next == 'd' || *next == 'D' || 227 *next == 'q' || *next == 'Q')) { 228 // Optional exponent letter. Blanks are allowed between the 229 // optional exponent letter and the exponent value. 230 io.SkipSpaces(remaining); 231 next = io.NextInField(remaining, edit); 232 } 233 // The default exponent is -kP, but the scale factor doesn't affect 234 // an explicit exponent. 235 exponent = -edit.modes.scale; 236 if (next && 237 (*next == '-' || *next == '+' || (*next >= '0' && *next <= '9') || 238 *next == ' ' || *next == '\t')) { 239 bool negExpo{*next == '-'}; 240 if (negExpo || *next == '+') { 241 next = io.NextInField(remaining, edit); 242 } 243 for (exponent = 0; next; next = io.NextInField(remaining, edit)) { 244 if (*next >= '0' && *next <= '9') { 245 exponent = 10 * exponent + *next - '0'; 246 } else if (*next == ' ' || *next == '\t') { 247 if (bzMode) { 248 exponent = 10 * exponent; 249 } 250 } else { 251 break; 252 } 253 } 254 if (negExpo) { 255 exponent = -exponent; 256 } 257 } 258 if (decimalPoint) { 259 exponent += *decimalPoint; 260 } else { 261 // When no decimal point (or comma) appears in the value, the 'd' 262 // part of the edit descriptor must be interpreted as the number of 263 // digits in the value to be interpreted as being to the *right* of 264 // the assumed decimal point (13.7.2.3.2) 265 exponent += got - start - edit.digits.value_or(0); 266 } 267 } else { 268 // TODO: hex FP input 269 exponent = 0; 270 return 0; 271 } 272 // Consume the trailing ')' of a list-directed or NAMELIST complex 273 // input value. 274 if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) { 275 if (next && (*next == ' ' || *next == '\t')) { 276 next = io.NextInField(remaining, edit); 277 } 278 if (!next) { // NextInField fails on separators like ')' 279 std::size_t byteCount{0}; 280 next = io.GetCurrentChar(byteCount); 281 if (next && *next == ')') { 282 io.HandleRelativePosition(byteCount); 283 } 284 } 285 } else if (remaining) { 286 while (next && (*next == ' ' || *next == '\t')) { 287 next = io.NextInField(remaining, edit); 288 } 289 if (next) { 290 return 0; // error: unused nonblank character in fixed-width field 291 } 292 } 293 return got; 294 } 295 296 static void RaiseFPExceptions(decimal::ConversionResultFlags flags) { 297 #undef RAISE 298 #ifdef feraisexcept // a macro in some environments; omit std:: 299 #define RAISE feraiseexcept 300 #else 301 #define RAISE std::feraiseexcept 302 #endif 303 if (flags & decimal::ConversionResultFlags::Overflow) { 304 RAISE(FE_OVERFLOW); 305 } 306 if (flags & decimal::ConversionResultFlags::Inexact) { 307 RAISE(FE_INEXACT); 308 } 309 if (flags & decimal::ConversionResultFlags::Invalid) { 310 RAISE(FE_INVALID); 311 } 312 #undef RAISE 313 } 314 315 // If no special modes are in effect and the form of the input value 316 // that's present in the input stream is acceptable to the decimal->binary 317 // converter without modification, this fast path for real input 318 // saves time by avoiding memory copies and reformatting of the exponent. 319 template <int PRECISION> 320 static bool TryFastPathRealInput( 321 IoStatementState &io, const DataEdit &edit, void *n) { 322 if (edit.modes.editingFlags & (blankZero | decimalComma)) { 323 return false; 324 } 325 if (edit.modes.scale != 0) { 326 return false; 327 } 328 const char *str{nullptr}; 329 std::size_t got{io.GetNextInputBytes(str)}; 330 if (got == 0 || str == nullptr || 331 !io.GetConnectionState().recordLength.has_value()) { 332 return false; // could not access reliably-terminated input stream 333 } 334 const char *p{str}; 335 std::int64_t maxConsume{ 336 std::min<std::int64_t>(got, edit.width.value_or(got))}; 337 const char *limit{str + maxConsume}; 338 decimal::ConversionToBinaryResult<PRECISION> converted{ 339 decimal::ConvertToBinary<PRECISION>(p, edit.modes.round, limit)}; 340 if (converted.flags & decimal::Invalid) { 341 return false; 342 } 343 if (edit.digits.value_or(0) != 0) { 344 // Edit descriptor is Fw.d (or other) with d != 0, which 345 // implies scaling 346 const char *q{str}; 347 for (; q < limit; ++q) { 348 if (*q == '.' || *q == 'n' || *q == 'N') { 349 break; 350 } 351 } 352 if (q == limit) { 353 // No explicit decimal point, and not NaN/Inf. 354 return false; 355 } 356 } 357 for (; p < limit && (*p == ' ' || *p == '\t'); ++p) { 358 } 359 if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) { 360 // Need to consume a trailing ')' and any white space after 361 if (p >= limit || *p != ')') { 362 return false; 363 } 364 for (++p; p < limit && (*p == ' ' || *p == '\t'); ++p) { 365 } 366 } 367 if (edit.width && p < str + *edit.width) { 368 return false; // unconverted characters remain in fixed width field 369 } 370 // Success on the fast path! 371 *reinterpret_cast<decimal::BinaryFloatingPointNumber<PRECISION> *>(n) = 372 converted.binary; 373 io.HandleRelativePosition(p - str); 374 // Set FP exception flags 375 if (converted.flags != decimal::ConversionResultFlags::Exact) { 376 RaiseFPExceptions(converted.flags); 377 } 378 return true; 379 } 380 381 template <int KIND> 382 bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) { 383 constexpr int binaryPrecision{common::PrecisionOfRealKind(KIND)}; 384 if (TryFastPathRealInput<binaryPrecision>(io, edit, n)) { 385 return true; 386 } 387 // Fast path wasn't available or didn't work; go the more general route 388 static constexpr int maxDigits{ 389 common::MaxDecimalConversionDigits(binaryPrecision)}; 390 static constexpr int bufferSize{maxDigits + 18}; 391 char buffer[bufferSize]; 392 int exponent{0}; 393 int got{ScanRealInput(buffer, maxDigits + 2, io, edit, exponent)}; 394 if (got >= maxDigits + 2) { 395 io.GetIoErrorHandler().Crash("EditCommonRealInput: buffer was too small"); 396 return false; 397 } 398 if (got == 0) { 399 io.GetIoErrorHandler().SignalError(IostatBadRealInput); 400 return false; 401 } 402 bool hadExtra{got > maxDigits}; 403 if (exponent != 0) { 404 buffer[got++] = 'e'; 405 if (exponent < 0) { 406 buffer[got++] = '-'; 407 exponent = -exponent; 408 } 409 if (exponent > 9999) { 410 exponent = 9999; // will convert to +/-Inf 411 } 412 if (exponent > 999) { 413 int dig{exponent / 1000}; 414 buffer[got++] = '0' + dig; 415 int rest{exponent - 1000 * dig}; 416 dig = rest / 100; 417 buffer[got++] = '0' + dig; 418 rest -= 100 * dig; 419 dig = rest / 10; 420 buffer[got++] = '0' + dig; 421 buffer[got++] = '0' + (rest - 10 * dig); 422 } else if (exponent > 99) { 423 int dig{exponent / 100}; 424 buffer[got++] = '0' + dig; 425 int rest{exponent - 100 * dig}; 426 dig = rest / 10; 427 buffer[got++] = '0' + dig; 428 buffer[got++] = '0' + (rest - 10 * dig); 429 } else if (exponent > 9) { 430 int dig{exponent / 10}; 431 buffer[got++] = '0' + dig; 432 buffer[got++] = '0' + (exponent - 10 * dig); 433 } else { 434 buffer[got++] = '0' + exponent; 435 } 436 } 437 buffer[got] = '\0'; 438 const char *p{buffer}; 439 decimal::ConversionToBinaryResult<binaryPrecision> converted{ 440 decimal::ConvertToBinary<binaryPrecision>(p, edit.modes.round)}; 441 if (hadExtra) { 442 converted.flags = static_cast<enum decimal::ConversionResultFlags>( 443 converted.flags | decimal::Inexact); 444 } 445 if (*p) { // unprocessed junk after value 446 io.GetIoErrorHandler().SignalError(IostatBadRealInput); 447 return false; 448 } 449 *reinterpret_cast<decimal::BinaryFloatingPointNumber<binaryPrecision> *>(n) = 450 converted.binary; 451 // Set FP exception flags 452 if (converted.flags != decimal::ConversionResultFlags::Exact) { 453 RaiseFPExceptions(converted.flags); 454 } 455 return true; 456 } 457 458 template <int KIND> 459 bool EditRealInput(IoStatementState &io, const DataEdit &edit, void *n) { 460 constexpr int binaryPrecision{common::PrecisionOfRealKind(KIND)}; 461 switch (edit.descriptor) { 462 case DataEdit::ListDirected: 463 if (IsNamelistName(io)) { 464 return false; 465 } 466 return EditCommonRealInput<KIND>(io, edit, n); 467 case DataEdit::ListDirectedRealPart: 468 case DataEdit::ListDirectedImaginaryPart: 469 case 'F': 470 case 'E': // incl. EN, ES, & EX 471 case 'D': 472 case 'G': 473 return EditCommonRealInput<KIND>(io, edit, n); 474 case 'B': 475 return EditBOZInput( 476 io, edit, n, 2, common::BitsForBinaryPrecision(binaryPrecision)); 477 case 'O': 478 return EditBOZInput( 479 io, edit, n, 8, common::BitsForBinaryPrecision(binaryPrecision)); 480 case 'Z': 481 return EditBOZInput( 482 io, edit, n, 16, common::BitsForBinaryPrecision(binaryPrecision)); 483 case 'A': // legacy extension 484 return EditCharacterInput(io, edit, reinterpret_cast<char *>(n), KIND); 485 default: 486 io.GetIoErrorHandler().SignalError(IostatErrorInFormat, 487 "Data edit descriptor '%c' may not be used for REAL input", 488 edit.descriptor); 489 return false; 490 } 491 } 492 493 // 13.7.3 in Fortran 2018 494 bool EditLogicalInput(IoStatementState &io, const DataEdit &edit, bool &x) { 495 switch (edit.descriptor) { 496 case DataEdit::ListDirected: 497 if (IsNamelistName(io)) { 498 return false; 499 } 500 break; 501 case 'L': 502 case 'G': 503 break; 504 default: 505 io.GetIoErrorHandler().SignalError(IostatErrorInFormat, 506 "Data edit descriptor '%c' may not be used for LOGICAL input", 507 edit.descriptor); 508 return false; 509 } 510 std::optional<int> remaining; 511 std::optional<char32_t> next{io.PrepareInput(edit, remaining)}; 512 if (next && *next == '.') { // skip optional period 513 next = io.NextInField(remaining, edit); 514 } 515 if (!next) { 516 io.GetIoErrorHandler().SignalError("Empty LOGICAL input field"); 517 return false; 518 } 519 switch (*next) { 520 case 'T': 521 case 't': 522 x = true; 523 break; 524 case 'F': 525 case 'f': 526 x = false; 527 break; 528 default: 529 io.GetIoErrorHandler().SignalError( 530 "Bad character '%lc' in LOGICAL input field", *next); 531 return false; 532 } 533 if (remaining) { // ignore the rest of the field 534 io.HandleRelativePosition(*remaining); 535 } else if (edit.descriptor == DataEdit::ListDirected) { 536 while (io.NextInField(remaining, edit)) { // discard rest of field 537 } 538 } 539 return true; 540 } 541 542 // See 13.10.3.1 paragraphs 7-9 in Fortran 2018 543 template <typename CHAR> 544 static bool EditDelimitedCharacterInput( 545 IoStatementState &io, CHAR *x, std::size_t length, char32_t delimiter) { 546 bool result{true}; 547 while (true) { 548 std::size_t byteCount{0}; 549 auto ch{io.GetCurrentChar(byteCount)}; 550 if (!ch) { 551 if (io.AdvanceRecord()) { 552 continue; 553 } else { 554 result = false; // EOF in character value 555 break; 556 } 557 } 558 io.HandleRelativePosition(byteCount); 559 if (*ch == delimiter) { 560 auto next{io.GetCurrentChar(byteCount)}; 561 if (next && *next == delimiter) { 562 // Repeated delimiter: use as character value 563 io.HandleRelativePosition(byteCount); 564 } else { 565 break; // closing delimiter 566 } 567 } 568 if (length > 0) { 569 *x++ = *ch; 570 --length; 571 } 572 } 573 std::fill_n(x, length, ' '); 574 return result; 575 } 576 577 template <typename CHAR> 578 static bool EditListDirectedCharacterInput( 579 IoStatementState &io, CHAR *x, std::size_t length, const DataEdit &edit) { 580 std::size_t byteCount{0}; 581 auto ch{io.GetCurrentChar(byteCount)}; 582 if (ch && (*ch == '\'' || *ch == '"')) { 583 io.HandleRelativePosition(byteCount); 584 return EditDelimitedCharacterInput(io, x, length, *ch); 585 } 586 if (IsNamelistName(io) || io.GetConnectionState().IsAtEOF()) { 587 return false; 588 } 589 // Undelimited list-directed character input: stop at a value separator 590 // or the end of the current record. Subtlety: the "remaining" count 591 // here is a dummy that's used to avoid the interpretation of separators 592 // in NextInField. 593 std::optional<int> remaining{maxUTF8Bytes}; 594 while (std::optional<char32_t> next{io.NextInField(remaining, edit)}) { 595 switch (*next) { 596 case ' ': 597 case '\t': 598 case ',': 599 case ';': 600 case '/': 601 remaining = 0; // value separator: stop 602 break; 603 default: 604 *x++ = *next; 605 --length; 606 remaining = maxUTF8Bytes; 607 } 608 } 609 std::fill_n(x, length, ' '); 610 return true; 611 } 612 613 template <typename CHAR> 614 bool EditCharacterInput( 615 IoStatementState &io, const DataEdit &edit, CHAR *x, std::size_t length) { 616 switch (edit.descriptor) { 617 case DataEdit::ListDirected: 618 return EditListDirectedCharacterInput(io, x, length, edit); 619 case 'A': 620 case 'G': 621 break; 622 default: 623 io.GetIoErrorHandler().SignalError(IostatErrorInFormat, 624 "Data edit descriptor '%c' may not be used with a CHARACTER data item", 625 edit.descriptor); 626 return false; 627 } 628 const ConnectionState &connection{io.GetConnectionState()}; 629 if (connection.IsAtEOF()) { 630 return false; 631 } 632 std::size_t remaining{length}; 633 if (edit.width && *edit.width > 0) { 634 remaining = *edit.width; 635 } 636 // When the field is wider than the variable, we drop the leading 637 // characters. When the variable is wider than the field, there's 638 // trailing padding. 639 const char *input{nullptr}; 640 std::size_t ready{0}; 641 bool hitEnd{false}; 642 // Skip leading bytes. 643 // These bytes don't count towards INQUIRE(IOLENGTH=). 644 std::size_t skip{remaining > length ? remaining - length : 0}; 645 // Transfer payload bytes; these do count. 646 while (remaining > 0) { 647 if (ready == 0) { 648 ready = io.GetNextInputBytes(input); 649 if (ready == 0) { 650 hitEnd = true; 651 break; 652 } 653 } 654 std::size_t chunk; 655 bool skipping{skip > 0}; 656 if (connection.isUTF8) { 657 chunk = MeasureUTF8Bytes(*input); 658 if (skipping) { 659 --skip; 660 } else if (auto ucs{DecodeUTF8(input)}) { 661 *x++ = *ucs; 662 --length; 663 } else if (chunk == 0) { 664 // error recovery: skip bad encoding 665 chunk = 1; 666 } 667 --remaining; 668 } else { 669 if (skipping) { 670 chunk = std::min<std::size_t>(skip, ready); 671 skip -= chunk; 672 } else { 673 chunk = std::min<std::size_t>(remaining, ready); 674 std::memcpy(x, input, chunk); 675 x += chunk; 676 length -= chunk; 677 } 678 remaining -= chunk; 679 } 680 input += chunk; 681 if (!skipping) { 682 io.GotChar(chunk); 683 } 684 io.HandleRelativePosition(chunk); 685 ready -= chunk; 686 } 687 // Pad the remainder of the input variable, if any. 688 std::fill_n(x, length, ' '); 689 if (hitEnd) { 690 io.CheckForEndOfRecord(); // signal any needed error 691 } 692 return true; 693 } 694 695 template bool EditRealInput<2>(IoStatementState &, const DataEdit &, void *); 696 template bool EditRealInput<3>(IoStatementState &, const DataEdit &, void *); 697 template bool EditRealInput<4>(IoStatementState &, const DataEdit &, void *); 698 template bool EditRealInput<8>(IoStatementState &, const DataEdit &, void *); 699 template bool EditRealInput<10>(IoStatementState &, const DataEdit &, void *); 700 // TODO: double/double 701 template bool EditRealInput<16>(IoStatementState &, const DataEdit &, void *); 702 703 template bool EditCharacterInput( 704 IoStatementState &, const DataEdit &, char *, std::size_t); 705 template bool EditCharacterInput( 706 IoStatementState &, const DataEdit &, char16_t *, std::size_t); 707 template bool EditCharacterInput( 708 IoStatementState &, const DataEdit &, char32_t *, std::size_t); 709 710 } // namespace Fortran::runtime::io 711