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