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