xref: /llvm-project/flang/runtime/edit-input.cpp (revision f1dbf8e4ada7761ba296400acbf0190e6e203dc6)
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