1 //===-- runtime/edit-input.cpp --------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "edit-input.h"
12 #include "flang/Common/real.h"
13 #include "flang/Common/uint128.h"
17 namespace Fortran::runtime::io
{
19 template <int LOG2_BASE
>
20 static bool EditBOZInput(
21 IoStatementState
&io
, const DataEdit
&edit
, void *n
, std::size_t bytes
) {
22 std::optional
<int> remaining
;
23 std::optional
<char32_t
> next
{io
.PrepareInput(edit
, remaining
)};
24 if (next
.value_or('?') == '0') {
26 next
= io
.NextInField(remaining
, edit
);
27 } while (next
&& *next
== '0');
29 // Count significant digits after any leading white space & zeroes
32 for (; next
; next
= io
.NextInField(remaining
, edit
)) {
35 if (ch
== ' ' || ch
== '\t') {
38 if (ch
>= '0' && ch
<= '1') {
39 } else if (LOG2_BASE
>= 3 && ch
>= '2' && ch
<= '7') {
40 } else if (LOG2_BASE
>= 4 && ch
>= '8' && ch
<= '9') {
41 } else if (LOG2_BASE
>= 4 && ch
>= 'A' && ch
<= 'F') {
42 } else if (LOG2_BASE
>= 4 && ch
>= 'a' && ch
<= 'f') {
44 io
.GetIoErrorHandler().SignalError(
45 "Bad character '%lc' in B/O/Z input field", ch
);
50 auto significantBytes
{static_cast<std::size_t>(digits
* LOG2_BASE
+ 7) / 8};
51 if (significantBytes
> bytes
) {
52 io
.GetIoErrorHandler().SignalError(IostatBOZInputOverflow
,
53 "B/O/Z input of %d digits overflows %zd-byte variable", digits
, bytes
);
56 // Reset to start of significant digits
57 io
.HandleRelativePosition(-chars
);
59 // Make a second pass now that the digit count is known
60 std::memset(n
, 0, bytes
);
61 int increment
{isHostLittleEndian
? -1 : 1};
62 auto *data
{reinterpret_cast<unsigned char *>(n
) +
63 (isHostLittleEndian
? significantBytes
- 1 : 0)};
64 int shift
{((digits
- 1) * LOG2_BASE
) & 7};
65 if (shift
+ LOG2_BASE
> 8) {
66 shift
-= 8; // misaligned octal
69 char32_t ch
{*io
.NextInField(remaining
, edit
)};
71 if (ch
>= '0' && ch
<= '9') {
73 } else if (ch
>= 'A' && ch
<= 'F') {
74 digit
= ch
+ 10 - 'A';
75 } else if (ch
>= 'a' && ch
<= 'f') {
76 digit
= ch
+ 10 - 'a';
83 if (shift
+ LOG2_BASE
> 8) { // misaligned octal
84 *data
|= digit
>> (8 - shift
);
88 *data
|= digit
<< shift
;
94 static inline char32_t
GetDecimalPoint(const DataEdit
&edit
) {
95 return edit
.modes
.editingFlags
& decimalComma
? char32_t
{','} : char32_t
{'.'};
98 // Prepares input from a field, and consumes the sign, if any.
99 // Returns true if there's a '-' sign.
100 static bool ScanNumericPrefix(IoStatementState
&io
, const DataEdit
&edit
,
101 std::optional
<char32_t
> &next
, std::optional
<int> &remaining
) {
102 next
= io
.PrepareInput(edit
, remaining
);
103 bool negative
{false};
105 negative
= *next
== '-';
106 if (negative
|| *next
== '+') {
107 io
.SkipSpaces(remaining
);
108 next
= io
.NextInField(remaining
, edit
);
114 bool EditIntegerInput(
115 IoStatementState
&io
, const DataEdit
&edit
, void *n
, int kind
) {
116 RUNTIME_CHECK(io
.GetIoErrorHandler(), kind
>= 1 && !(kind
& (kind
- 1)));
117 switch (edit
.descriptor
) {
118 case DataEdit::ListDirected
:
119 if (IsNamelistName(io
)) {
127 return EditBOZInput
<1>(io
, edit
, n
, kind
);
129 return EditBOZInput
<3>(io
, edit
, n
, kind
);
131 return EditBOZInput
<4>(io
, edit
, n
, kind
);
132 case 'A': // legacy extension
133 return EditCharacterInput(io
, edit
, reinterpret_cast<char *>(n
), kind
);
135 io
.GetIoErrorHandler().SignalError(IostatErrorInFormat
,
136 "Data edit descriptor '%c' may not be used with an INTEGER data item",
140 std::optional
<int> remaining
;
141 std::optional
<char32_t
> next
;
142 bool negate
{ScanNumericPrefix(io
, edit
, next
, remaining
)};
143 common::UnsignedInt128 value
{0};
145 bool overflow
{false};
146 for (; next
; next
= io
.NextInField(remaining
, edit
)) {
148 if (ch
== ' ' || ch
== '\t') {
149 if (edit
.modes
.editingFlags
& blankZero
) {
150 ch
= '0'; // BZ mode - treat blank as if it were zero
156 if (ch
>= '0' && ch
<= '9') {
159 io
.GetIoErrorHandler().SignalError(
160 "Bad character '%lc' in INTEGER input field", ch
);
163 static constexpr auto maxu128
{~common::UnsignedInt128
{0}};
164 static constexpr auto maxu128OverTen
{maxu128
/ 10};
165 static constexpr int maxLastDigit
{
166 static_cast<int>(maxu128
- (maxu128OverTen
* 10))};
167 overflow
|= value
>= maxu128OverTen
&&
168 (value
> maxu128OverTen
|| digit
> maxLastDigit
);
173 auto maxForKind
{common::UnsignedInt128
{1} << ((8 * kind
) - 1)};
174 overflow
|= value
>= maxForKind
&& (value
> maxForKind
|| !negate
);
176 io
.GetIoErrorHandler().SignalError(IostatIntegerInputOverflow
,
177 "Decimal input overflows INTEGER(%d) variable", kind
);
183 if (any
|| !io
.GetConnectionState().IsAtEOF()) {
184 std::memcpy(n
, &value
, kind
); // a blank field means zero
189 // Parses a REAL input number from the input source as a normalized
190 // fraction into a supplied buffer -- there's an optional '-', a
191 // decimal point, and at least one digit. The adjusted exponent value
192 // is returned in a reference argument. The returned value is the number
193 // of characters that (should) have been written to the buffer -- this can
194 // be larger than the buffer size and can indicate overflow. Replaces
195 // blanks with zeroes if appropriate.
196 static int ScanRealInput(char *buffer
, int bufferSize
, IoStatementState
&io
,
197 const DataEdit
&edit
, int &exponent
) {
198 std::optional
<int> remaining
;
199 std::optional
<char32_t
> next
;
201 std::optional
<int> decimalPoint
;
202 auto Put
{[&](char ch
) -> void {
203 if (got
< bufferSize
) {
208 if (ScanNumericPrefix(io
, edit
, next
, remaining
)) {
211 bool bzMode
{(edit
.modes
.editingFlags
& blankZero
) != 0};
212 if (!next
|| (!bzMode
&& *next
== ' ')) { // empty/blank field means zero
214 if (!io
.GetConnectionState().IsAtEOF()) {
219 char32_t decimal
{GetDecimalPoint(edit
)};
220 char32_t first
{*next
>= 'a' && *next
<= 'z' ? *next
+ 'A' - 'a' : *next
};
221 if (first
== 'N' || first
== 'I') {
222 // NaN or infinity - convert to upper case
223 // Subtle: a blank field of digits could be followed by 'E' or 'D',
225 ((*next
>= 'a' && *next
<= 'z') || (*next
>= 'A' && *next
<= 'Z'));
226 next
= io
.NextInField(remaining
, edit
)) {
227 if (*next
>= 'a' && *next
<= 'z') {
228 Put(*next
- 'a' + 'A');
233 if (next
&& *next
== '(') { // NaN(...)
237 next
= io
.NextInField(remaining
, edit
);
242 } else if (*next
== '(') {
244 } else if (*next
== ')') {
251 } else if (first
== decimal
|| (first
>= '0' && first
<= '9') ||
252 (bzMode
&& (first
== ' ' || first
== '\t')) || first
== 'E' ||
253 first
== 'D' || first
== 'Q') {
254 Put('.'); // input field is normalized to a fraction
256 for (; next
; next
= io
.NextInField(remaining
, edit
)) {
258 if (ch
== ' ' || ch
== '\t') {
260 ch
= '0'; // BZ mode - treat blank as if it were zero
265 if (ch
== '0' && got
== start
&& !decimalPoint
) {
266 // omit leading zeroes before the decimal
267 } else if (ch
>= '0' && ch
<= '9') {
269 } else if (ch
== decimal
&& !decimalPoint
) {
270 // the decimal point is *not* copied to the buffer
271 decimalPoint
= got
- start
; // # of digits before the decimal point
277 // Nothing but zeroes and maybe a decimal point. F'2018 requires
278 // at least one digit, but F'77 did not, and a bare "." shows up in
280 Put('0'); // emit at least one digit
283 (*next
== 'e' || *next
== 'E' || *next
== 'd' || *next
== 'D' ||
284 *next
== 'q' || *next
== 'Q')) {
285 // Optional exponent letter. Blanks are allowed between the
286 // optional exponent letter and the exponent value.
287 io
.SkipSpaces(remaining
);
288 next
= io
.NextInField(remaining
, edit
);
290 // The default exponent is -kP, but the scale factor doesn't affect
291 // an explicit exponent.
292 exponent
= -edit
.modes
.scale
;
294 (*next
== '-' || *next
== '+' || (*next
>= '0' && *next
<= '9') ||
295 *next
== ' ' || *next
== '\t')) {
296 bool negExpo
{*next
== '-'};
297 if (negExpo
|| *next
== '+') {
298 next
= io
.NextInField(remaining
, edit
);
300 for (exponent
= 0; next
; next
= io
.NextInField(remaining
, edit
)) {
301 if (*next
>= '0' && *next
<= '9') {
302 if (exponent
< 10000) {
303 exponent
= 10 * exponent
+ *next
- '0';
305 } else if (*next
== ' ' || *next
== '\t') {
307 exponent
= 10 * exponent
;
314 exponent
= -exponent
;
318 exponent
+= *decimalPoint
;
320 // When no decimal point (or comma) appears in the value, the 'd'
321 // part of the edit descriptor must be interpreted as the number of
322 // digits in the value to be interpreted as being to the *right* of
323 // the assumed decimal point (13.7.2.3.2)
324 exponent
+= got
- start
- edit
.digits
.value_or(0);
327 // TODO: hex FP input
331 // Consume the trailing ')' of a list-directed or NAMELIST complex
333 if (edit
.descriptor
== DataEdit::ListDirectedImaginaryPart
) {
334 if (next
&& (*next
== ' ' || *next
== '\t')) {
335 next
= io
.NextInField(remaining
, edit
);
337 if (!next
) { // NextInField fails on separators like ')'
338 std::size_t byteCount
{0};
339 next
= io
.GetCurrentChar(byteCount
);
340 if (next
&& *next
== ')') {
341 io
.HandleRelativePosition(byteCount
);
344 } else if (remaining
) {
345 while (next
&& (*next
== ' ' || *next
== '\t')) {
346 next
= io
.NextInField(remaining
, edit
);
349 return 0; // error: unused nonblank character in fixed-width field
355 static void RaiseFPExceptions(decimal::ConversionResultFlags flags
) {
357 #ifdef feraisexcept // a macro in some environments; omit std::
358 #define RAISE feraiseexcept
360 #define RAISE std::feraiseexcept
362 if (flags
& decimal::ConversionResultFlags::Overflow
) {
365 if (flags
& decimal::ConversionResultFlags::Inexact
) {
368 if (flags
& decimal::ConversionResultFlags::Invalid
) {
374 // If no special modes are in effect and the form of the input value
375 // that's present in the input stream is acceptable to the decimal->binary
376 // converter without modification, this fast path for real input
377 // saves time by avoiding memory copies and reformatting of the exponent.
378 template <int PRECISION
>
379 static bool TryFastPathRealInput(
380 IoStatementState
&io
, const DataEdit
&edit
, void *n
) {
381 if (edit
.modes
.editingFlags
& (blankZero
| decimalComma
)) {
384 if (edit
.modes
.scale
!= 0) {
387 const char *str
{nullptr};
388 std::size_t got
{io
.GetNextInputBytes(str
)};
389 if (got
== 0 || str
== nullptr ||
390 !io
.GetConnectionState().recordLength
.has_value()) {
391 return false; // could not access reliably-terminated input stream
394 std::int64_t maxConsume
{
395 std::min
<std::int64_t>(got
, edit
.width
.value_or(got
))};
396 const char *limit
{str
+ maxConsume
};
397 decimal::ConversionToBinaryResult
<PRECISION
> converted
{
398 decimal::ConvertToBinary
<PRECISION
>(p
, edit
.modes
.round
, limit
)};
399 if (converted
.flags
& (decimal::Invalid
| decimal::Overflow
)) {
402 if (edit
.digits
.value_or(0) != 0) {
403 // Edit descriptor is Fw.d (or other) with d != 0, which
406 for (; q
< limit
; ++q
) {
407 if (*q
== '.' || *q
== 'n' || *q
== 'N') {
412 // No explicit decimal point, and not NaN/Inf.
416 for (; p
< limit
&& (*p
== ' ' || *p
== '\t'); ++p
) {
418 if (edit
.descriptor
== DataEdit::ListDirectedImaginaryPart
) {
419 // Need to consume a trailing ')' and any white space after
420 if (p
>= limit
|| *p
!= ')') {
423 for (++p
; p
< limit
&& (*p
== ' ' || *p
== '\t'); ++p
) {
426 if (edit
.width
&& p
< str
+ *edit
.width
) {
427 return false; // unconverted characters remain in fixed width field
429 // Success on the fast path!
430 *reinterpret_cast<decimal::BinaryFloatingPointNumber
<PRECISION
> *>(n
) =
432 io
.HandleRelativePosition(p
- str
);
433 // Set FP exception flags
434 if (converted
.flags
!= decimal::ConversionResultFlags::Exact
) {
435 RaiseFPExceptions(converted
.flags
);
441 bool EditCommonRealInput(IoStatementState
&io
, const DataEdit
&edit
, void *n
) {
442 constexpr int binaryPrecision
{common::PrecisionOfRealKind(KIND
)};
443 if (TryFastPathRealInput
<binaryPrecision
>(io
, edit
, n
)) {
446 // Fast path wasn't available or didn't work; go the more general route
447 static constexpr int maxDigits
{
448 common::MaxDecimalConversionDigits(binaryPrecision
)};
449 static constexpr int bufferSize
{maxDigits
+ 18};
450 char buffer
[bufferSize
];
452 int got
{ScanRealInput(buffer
, maxDigits
+ 2, io
, edit
, exponent
)};
453 if (got
>= maxDigits
+ 2) {
454 io
.GetIoErrorHandler().Crash("EditCommonRealInput: buffer was too small");
458 io
.GetIoErrorHandler().SignalError(IostatBadRealInput
);
461 bool hadExtra
{got
> maxDigits
};
466 exponent
= -exponent
;
468 if (exponent
> 9999) {
469 exponent
= 9999; // will convert to +/-Inf
471 if (exponent
> 999) {
472 int dig
{exponent
/ 1000};
473 buffer
[got
++] = '0' + dig
;
474 int rest
{exponent
- 1000 * dig
};
476 buffer
[got
++] = '0' + dig
;
479 buffer
[got
++] = '0' + dig
;
480 buffer
[got
++] = '0' + (rest
- 10 * dig
);
481 } else if (exponent
> 99) {
482 int dig
{exponent
/ 100};
483 buffer
[got
++] = '0' + dig
;
484 int rest
{exponent
- 100 * dig
};
486 buffer
[got
++] = '0' + dig
;
487 buffer
[got
++] = '0' + (rest
- 10 * dig
);
488 } else if (exponent
> 9) {
489 int dig
{exponent
/ 10};
490 buffer
[got
++] = '0' + dig
;
491 buffer
[got
++] = '0' + (exponent
- 10 * dig
);
493 buffer
[got
++] = '0' + exponent
;
497 const char *p
{buffer
};
498 decimal::ConversionToBinaryResult
<binaryPrecision
> converted
{
499 decimal::ConvertToBinary
<binaryPrecision
>(p
, edit
.modes
.round
)};
501 converted
.flags
= static_cast<enum decimal::ConversionResultFlags
>(
502 converted
.flags
| decimal::Inexact
);
504 if (*p
) { // unprocessed junk after value
505 io
.GetIoErrorHandler().SignalError(IostatBadRealInput
);
508 *reinterpret_cast<decimal::BinaryFloatingPointNumber
<binaryPrecision
> *>(n
) =
510 // Set FP exception flags
511 if (converted
.flags
!= decimal::ConversionResultFlags::Exact
) {
512 if (converted
.flags
& decimal::ConversionResultFlags::Overflow
) {
513 io
.GetIoErrorHandler().SignalError(IostatRealInputOverflow
);
516 RaiseFPExceptions(converted
.flags
);
522 bool EditRealInput(IoStatementState
&io
, const DataEdit
&edit
, void *n
) {
523 switch (edit
.descriptor
) {
524 case DataEdit::ListDirected
:
525 if (IsNamelistName(io
)) {
528 return EditCommonRealInput
<KIND
>(io
, edit
, n
);
529 case DataEdit::ListDirectedRealPart
:
530 case DataEdit::ListDirectedImaginaryPart
:
532 case 'E': // incl. EN, ES, & EX
535 return EditCommonRealInput
<KIND
>(io
, edit
, n
);
537 return EditBOZInput
<1>(io
, edit
, n
,
538 common::BitsForBinaryPrecision(common::PrecisionOfRealKind(KIND
)) >> 3);
540 return EditBOZInput
<3>(io
, edit
, n
,
541 common::BitsForBinaryPrecision(common::PrecisionOfRealKind(KIND
)) >> 3);
543 return EditBOZInput
<4>(io
, edit
, n
,
544 common::BitsForBinaryPrecision(common::PrecisionOfRealKind(KIND
)) >> 3);
545 case 'A': // legacy extension
546 return EditCharacterInput(io
, edit
, reinterpret_cast<char *>(n
), KIND
);
548 io
.GetIoErrorHandler().SignalError(IostatErrorInFormat
,
549 "Data edit descriptor '%c' may not be used for REAL input",
555 // 13.7.3 in Fortran 2018
556 bool EditLogicalInput(IoStatementState
&io
, const DataEdit
&edit
, bool &x
) {
557 switch (edit
.descriptor
) {
558 case DataEdit::ListDirected
:
559 if (IsNamelistName(io
)) {
567 io
.GetIoErrorHandler().SignalError(IostatErrorInFormat
,
568 "Data edit descriptor '%c' may not be used for LOGICAL input",
572 std::optional
<int> remaining
;
573 std::optional
<char32_t
> next
{io
.PrepareInput(edit
, remaining
)};
574 if (next
&& *next
== '.') { // skip optional period
575 next
= io
.NextInField(remaining
, edit
);
578 io
.GetIoErrorHandler().SignalError("Empty LOGICAL input field");
591 io
.GetIoErrorHandler().SignalError(
592 "Bad character '%lc' in LOGICAL input field", *next
);
595 if (remaining
) { // ignore the rest of the field
596 io
.HandleRelativePosition(*remaining
);
597 } else if (edit
.descriptor
== DataEdit::ListDirected
) {
598 while (io
.NextInField(remaining
, edit
)) { // discard rest of field
604 // See 13.10.3.1 paragraphs 7-9 in Fortran 2018
605 template <typename CHAR
>
606 static bool EditDelimitedCharacterInput(
607 IoStatementState
&io
, CHAR
*x
, std::size_t length
, char32_t delimiter
) {
610 std::size_t byteCount
{0};
611 auto ch
{io
.GetCurrentChar(byteCount
)};
613 if (io
.AdvanceRecord()) {
616 result
= false; // EOF in character value
620 io
.HandleRelativePosition(byteCount
);
621 if (*ch
== delimiter
) {
622 auto next
{io
.GetCurrentChar(byteCount
)};
623 if (next
&& *next
== delimiter
) {
624 // Repeated delimiter: use as character value
625 io
.HandleRelativePosition(byteCount
);
627 break; // closing delimiter
635 std::fill_n(x
, length
, ' ');
639 template <typename CHAR
>
640 static bool EditListDirectedCharacterInput(
641 IoStatementState
&io
, CHAR
*x
, std::size_t length
, const DataEdit
&edit
) {
642 std::size_t byteCount
{0};
643 auto ch
{io
.GetCurrentChar(byteCount
)};
644 if (ch
&& (*ch
== '\'' || *ch
== '"')) {
645 io
.HandleRelativePosition(byteCount
);
646 return EditDelimitedCharacterInput(io
, x
, length
, *ch
);
648 if (IsNamelistName(io
) || io
.GetConnectionState().IsAtEOF()) {
651 // Undelimited list-directed character input: stop at a value separator
652 // or the end of the current record. Subtlety: the "remaining" count
653 // here is a dummy that's used to avoid the interpretation of separators
655 std::optional
<int> remaining
{length
> 0 ? maxUTF8Bytes
: 0};
656 while (std::optional
<char32_t
> next
{io
.NextInField(remaining
, edit
)}) {
665 isSep
= !(edit
.modes
.editingFlags
& decimalComma
);
668 isSep
= !!(edit
.modes
.editingFlags
& decimalComma
);
677 remaining
= --length
> 0 ? maxUTF8Bytes
: 0;
680 std::fill_n(x
, length
, ' ');
684 template <typename CHAR
>
685 bool EditCharacterInput(
686 IoStatementState
&io
, const DataEdit
&edit
, CHAR
*x
, std::size_t length
) {
687 switch (edit
.descriptor
) {
688 case DataEdit::ListDirected
:
689 return EditListDirectedCharacterInput(io
, x
, length
, edit
);
694 return EditBOZInput
<1>(io
, edit
, x
, length
* sizeof *x
);
696 return EditBOZInput
<3>(io
, edit
, x
, length
* sizeof *x
);
698 return EditBOZInput
<4>(io
, edit
, x
, length
* sizeof *x
);
700 io
.GetIoErrorHandler().SignalError(IostatErrorInFormat
,
701 "Data edit descriptor '%c' may not be used with a CHARACTER data item",
705 const ConnectionState
&connection
{io
.GetConnectionState()};
706 std::size_t remaining
{length
};
707 if (edit
.width
&& *edit
.width
> 0) {
708 remaining
= *edit
.width
;
710 // When the field is wider than the variable, we drop the leading
711 // characters. When the variable is wider than the field, there can be
713 const char *input
{nullptr};
714 std::size_t ready
{0};
715 // Skip leading bytes.
716 // These bytes don't count towards INQUIRE(IOLENGTH=).
717 std::size_t skip
{remaining
> length
? remaining
- length
: 0};
718 // Transfer payload bytes; these do count.
719 while (remaining
> 0) {
721 ready
= io
.GetNextInputBytes(input
);
723 if (io
.CheckForEndOfRecord()) {
724 std::fill_n(x
, length
, ' '); // PAD='YES'
726 return !io
.GetIoErrorHandler().InError();
730 bool skipping
{skip
> 0};
731 if (connection
.isUTF8
) {
732 chunk
= MeasureUTF8Bytes(*input
);
735 } else if (auto ucs
{DecodeUTF8(input
)}) {
738 } else if (chunk
== 0) {
739 // error recovery: skip bad encoding
743 } else if constexpr (sizeof *x
> 1) {
744 // Read single byte with expansion into multi-byte CHARACTER
749 *x
++ = static_cast<unsigned char>(*input
);
753 } else { // single bytes -> default CHARACTER
755 chunk
= std::min
<std::size_t>(skip
, ready
);
758 chunk
= std::min
<std::size_t>(remaining
, ready
);
759 std::memcpy(x
, input
, chunk
);
769 io
.HandleRelativePosition(chunk
);
772 // Pad the remainder of the input variable, if any.
773 std::fill_n(x
, length
, ' ');
777 template bool EditRealInput
<2>(IoStatementState
&, const DataEdit
&, void *);
778 template bool EditRealInput
<3>(IoStatementState
&, const DataEdit
&, void *);
779 template bool EditRealInput
<4>(IoStatementState
&, const DataEdit
&, void *);
780 template bool EditRealInput
<8>(IoStatementState
&, const DataEdit
&, void *);
781 template bool EditRealInput
<10>(IoStatementState
&, const DataEdit
&, void *);
782 // TODO: double/double
783 template bool EditRealInput
<16>(IoStatementState
&, const DataEdit
&, void *);
785 template bool EditCharacterInput(
786 IoStatementState
&, const DataEdit
&, char *, std::size_t);
787 template bool EditCharacterInput(
788 IoStatementState
&, const DataEdit
&, char16_t
*, std::size_t);
789 template bool EditCharacterInput(
790 IoStatementState
&, const DataEdit
&, char32_t
*, std::size_t);
792 } // namespace Fortran::runtime::io