xref: /llvm-project/flang/runtime/edit-input.cpp (revision cb193931fa05c5f51e4779c80ae8d2fe0c1c3ea9)
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 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') {
25     do {
26       next = io.NextInField(remaining, edit);
27     } while (next && *next == '0');
28   }
29   // Count significant digits after any leading white space & zeroes
30   int digits{0};
31   int chars{0};
32   for (; next; next = io.NextInField(remaining, edit)) {
33     ++chars;
34     char32_t ch{*next};
35     if (ch == ' ' || ch == '\t') {
36       continue;
37     }
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') {
43     } else {
44       io.GetIoErrorHandler().SignalError(
45           "Bad character '%lc' in B/O/Z input field", ch);
46       return false;
47     }
48     ++digits;
49   }
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);
54     return false;
55   }
56   // Reset to start of significant digits
57   io.HandleRelativePosition(-chars);
58   remaining.reset();
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
67   }
68   while (digits > 0) {
69     char32_t ch{*io.NextInField(remaining, edit)};
70     int digit{0};
71     if (ch >= '0' && ch <= '9') {
72       digit = ch - '0';
73     } else if (ch >= 'A' && ch <= 'F') {
74       digit = ch + 10 - 'A';
75     } else if (ch >= 'a' && ch <= 'f') {
76       digit = ch + 10 - 'a';
77     } else {
78       continue;
79     }
80     --digits;
81     if (shift < 0) {
82       shift += 8;
83       if (shift + LOG2_BASE > 8) { // misaligned octal
84         *data |= digit >> (8 - shift);
85       }
86       data += increment;
87     }
88     *data |= digit << shift;
89     shift -= LOG2_BASE;
90   }
91   return true;
92 }
93 
94 static inline char32_t GetDecimalPoint(const DataEdit &edit) {
95   return edit.modes.editingFlags & decimalComma ? char32_t{','} : char32_t{'.'};
96 }
97 
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};
104   if (next) {
105     negative = *next == '-';
106     if (negative || *next == '+') {
107       io.SkipSpaces(remaining);
108       next = io.NextInField(remaining, edit);
109     }
110   }
111   return negative;
112 }
113 
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)) {
120       return false;
121     }
122     break;
123   case 'G':
124   case 'I':
125     break;
126   case 'B':
127     return EditBOZInput<1>(io, edit, n, kind);
128   case 'O':
129     return EditBOZInput<3>(io, edit, n, kind);
130   case 'Z':
131     return EditBOZInput<4>(io, edit, n, kind);
132   case 'A': // legacy extension
133     return EditCharacterInput(io, edit, reinterpret_cast<char *>(n), kind);
134   default:
135     io.GetIoErrorHandler().SignalError(IostatErrorInFormat,
136         "Data edit descriptor '%c' may not be used with an INTEGER data item",
137         edit.descriptor);
138     return false;
139   }
140   std::optional<int> remaining;
141   std::optional<char32_t> next;
142   bool negate{ScanNumericPrefix(io, edit, next, remaining)};
143   common::UnsignedInt128 value{0};
144   bool any{negate};
145   bool overflow{false};
146   for (; next; next = io.NextInField(remaining, edit)) {
147     char32_t ch{*next};
148     if (ch == ' ' || ch == '\t') {
149       if (edit.modes.editingFlags & blankZero) {
150         ch = '0'; // BZ mode - treat blank as if it were zero
151       } else {
152         continue;
153       }
154     }
155     int digit{0};
156     if (ch >= '0' && ch <= '9') {
157       digit = ch - '0';
158     } else {
159       io.GetIoErrorHandler().SignalError(
160           "Bad character '%lc' in INTEGER input field", ch);
161       return false;
162     }
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);
169     value *= 10;
170     value += digit;
171     any = true;
172   }
173   auto maxForKind{common::UnsignedInt128{1} << ((8 * kind) - 1)};
174   overflow |= value >= maxForKind && (value > maxForKind || !negate);
175   if (overflow) {
176     io.GetIoErrorHandler().SignalError(IostatIntegerInputOverflow,
177         "Decimal input overflows INTEGER(%d) variable", kind);
178     return false;
179   }
180   if (negate) {
181     value = -value;
182   }
183   if (any || !io.GetConnectionState().IsAtEOF()) {
184     std::memcpy(n, &value, kind); // a blank field means zero
185   }
186   return any;
187 }
188 
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;
200   int got{0};
201   std::optional<int> decimalPoint;
202   auto Put{[&](char ch) -> void {
203     if (got < bufferSize) {
204       buffer[got] = ch;
205     }
206     ++got;
207   }};
208   if (ScanNumericPrefix(io, edit, next, remaining)) {
209     Put('-');
210   }
211   bool bzMode{(edit.modes.editingFlags & blankZero) != 0};
212   if (!next || (!bzMode && *next == ' ')) { // empty/blank field means zero
213     remaining.reset();
214     if (!io.GetConnectionState().IsAtEOF()) {
215       Put('0');
216     }
217     return got;
218   }
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',
224     for (; next &&
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');
229       } else {
230         Put(*next);
231       }
232     }
233     if (next && *next == '(') { // NaN(...)
234       Put('(');
235       int depth{1};
236       while (true) {
237         next = io.NextInField(remaining, edit);
238         if (depth == 0) {
239           break;
240         } else if (!next) {
241           return 0; // error
242         } else if (*next == '(') {
243           ++depth;
244         } else if (*next == ')') {
245           --depth;
246         }
247         Put(*next);
248       }
249     }
250     exponent = 0;
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
255     auto start{got};
256     for (; next; next = io.NextInField(remaining, edit)) {
257       char32_t ch{*next};
258       if (ch == ' ' || ch == '\t') {
259         if (bzMode) {
260           ch = '0'; // BZ mode - treat blank as if it were zero
261         } else {
262           continue;
263         }
264       }
265       if (ch == '0' && got == start && !decimalPoint) {
266         // omit leading zeroes before the decimal
267       } else if (ch >= '0' && ch <= '9') {
268         Put(ch);
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
272       } else {
273         break;
274       }
275     }
276     if (got == start) {
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
279       // the FCVS suite.
280       Put('0'); // emit at least one digit
281     }
282     if (next &&
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);
289     }
290     // The default exponent is -kP, but the scale factor doesn't affect
291     // an explicit exponent.
292     exponent = -edit.modes.scale;
293     if (next &&
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);
299       }
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';
304           }
305         } else if (*next == ' ' || *next == '\t') {
306           if (bzMode) {
307             exponent = 10 * exponent;
308           }
309         } else {
310           break;
311         }
312       }
313       if (negExpo) {
314         exponent = -exponent;
315       }
316     }
317     if (decimalPoint) {
318       exponent += *decimalPoint;
319     } else {
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);
325     }
326   } else {
327     // TODO: hex FP input
328     exponent = 0;
329     return 0;
330   }
331   // Consume the trailing ')' of a list-directed or NAMELIST complex
332   // input value.
333   if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) {
334     if (next && (*next == ' ' || *next == '\t')) {
335       next = io.NextInField(remaining, edit);
336     }
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);
342       }
343     }
344   } else if (remaining) {
345     while (next && (*next == ' ' || *next == '\t')) {
346       next = io.NextInField(remaining, edit);
347     }
348     if (next) {
349       return 0; // error: unused nonblank character in fixed-width field
350     }
351   }
352   return got;
353 }
354 
355 static void RaiseFPExceptions(decimal::ConversionResultFlags flags) {
356 #undef RAISE
357 #ifdef feraisexcept // a macro in some environments; omit std::
358 #define RAISE feraiseexcept
359 #else
360 #define RAISE std::feraiseexcept
361 #endif
362   if (flags & decimal::ConversionResultFlags::Overflow) {
363     RAISE(FE_OVERFLOW);
364   }
365   if (flags & decimal::ConversionResultFlags::Inexact) {
366     RAISE(FE_INEXACT);
367   }
368   if (flags & decimal::ConversionResultFlags::Invalid) {
369     RAISE(FE_INVALID);
370   }
371 #undef RAISE
372 }
373 
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)) {
382     return false;
383   }
384   if (edit.modes.scale != 0) {
385     return false;
386   }
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
392   }
393   const char *p{str};
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)) {
400     return false;
401   }
402   if (edit.digits.value_or(0) != 0) {
403     // Edit descriptor is Fw.d (or other) with d != 0, which
404     // implies scaling
405     const char *q{str};
406     for (; q < limit; ++q) {
407       if (*q == '.' || *q == 'n' || *q == 'N') {
408         break;
409       }
410     }
411     if (q == limit) {
412       // No explicit decimal point, and not NaN/Inf.
413       return false;
414     }
415   }
416   for (; p < limit && (*p == ' ' || *p == '\t'); ++p) {
417   }
418   if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) {
419     // Need to consume a trailing ')' and any white space after
420     if (p >= limit || *p != ')') {
421       return false;
422     }
423     for (++p; p < limit && (*p == ' ' || *p == '\t'); ++p) {
424     }
425   }
426   if (edit.width && p < str + *edit.width) {
427     return false; // unconverted characters remain in fixed width field
428   }
429   // Success on the fast path!
430   *reinterpret_cast<decimal::BinaryFloatingPointNumber<PRECISION> *>(n) =
431       converted.binary;
432   io.HandleRelativePosition(p - str);
433   // Set FP exception flags
434   if (converted.flags != decimal::ConversionResultFlags::Exact) {
435     RaiseFPExceptions(converted.flags);
436   }
437   return true;
438 }
439 
440 template <int KIND>
441 bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) {
442   constexpr int binaryPrecision{common::PrecisionOfRealKind(KIND)};
443   if (TryFastPathRealInput<binaryPrecision>(io, edit, n)) {
444     return true;
445   }
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];
451   int exponent{0};
452   int got{ScanRealInput(buffer, maxDigits + 2, io, edit, exponent)};
453   if (got >= maxDigits + 2) {
454     io.GetIoErrorHandler().Crash("EditCommonRealInput: buffer was too small");
455     return false;
456   }
457   if (got == 0) {
458     io.GetIoErrorHandler().SignalError(IostatBadRealInput);
459     return false;
460   }
461   bool hadExtra{got > maxDigits};
462   if (exponent != 0) {
463     buffer[got++] = 'e';
464     if (exponent < 0) {
465       buffer[got++] = '-';
466       exponent = -exponent;
467     }
468     if (exponent > 9999) {
469       exponent = 9999; // will convert to +/-Inf
470     }
471     if (exponent > 999) {
472       int dig{exponent / 1000};
473       buffer[got++] = '0' + dig;
474       int rest{exponent - 1000 * dig};
475       dig = rest / 100;
476       buffer[got++] = '0' + dig;
477       rest -= 100 * dig;
478       dig = rest / 10;
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};
485       dig = rest / 10;
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);
492     } else {
493       buffer[got++] = '0' + exponent;
494     }
495   }
496   buffer[got] = '\0';
497   const char *p{buffer};
498   decimal::ConversionToBinaryResult<binaryPrecision> converted{
499       decimal::ConvertToBinary<binaryPrecision>(p, edit.modes.round)};
500   if (hadExtra) {
501     converted.flags = static_cast<enum decimal::ConversionResultFlags>(
502         converted.flags | decimal::Inexact);
503   }
504   if (*p) { // unprocessed junk after value
505     io.GetIoErrorHandler().SignalError(IostatBadRealInput);
506     return false;
507   }
508   *reinterpret_cast<decimal::BinaryFloatingPointNumber<binaryPrecision> *>(n) =
509       converted.binary;
510   // Set FP exception flags
511   if (converted.flags != decimal::ConversionResultFlags::Exact) {
512     if (converted.flags & decimal::ConversionResultFlags::Overflow) {
513       io.GetIoErrorHandler().SignalError(IostatRealInputOverflow);
514       return false;
515     }
516     RaiseFPExceptions(converted.flags);
517   }
518   return true;
519 }
520 
521 template <int KIND>
522 bool EditRealInput(IoStatementState &io, const DataEdit &edit, void *n) {
523   switch (edit.descriptor) {
524   case DataEdit::ListDirected:
525     if (IsNamelistName(io)) {
526       return false;
527     }
528     return EditCommonRealInput<KIND>(io, edit, n);
529   case DataEdit::ListDirectedRealPart:
530   case DataEdit::ListDirectedImaginaryPart:
531   case 'F':
532   case 'E': // incl. EN, ES, & EX
533   case 'D':
534   case 'G':
535     return EditCommonRealInput<KIND>(io, edit, n);
536   case 'B':
537     return EditBOZInput<1>(io, edit, n,
538         common::BitsForBinaryPrecision(common::PrecisionOfRealKind(KIND)) >> 3);
539   case 'O':
540     return EditBOZInput<3>(io, edit, n,
541         common::BitsForBinaryPrecision(common::PrecisionOfRealKind(KIND)) >> 3);
542   case 'Z':
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);
547   default:
548     io.GetIoErrorHandler().SignalError(IostatErrorInFormat,
549         "Data edit descriptor '%c' may not be used for REAL input",
550         edit.descriptor);
551     return false;
552   }
553 }
554 
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)) {
560       return false;
561     }
562     break;
563   case 'L':
564   case 'G':
565     break;
566   default:
567     io.GetIoErrorHandler().SignalError(IostatErrorInFormat,
568         "Data edit descriptor '%c' may not be used for LOGICAL input",
569         edit.descriptor);
570     return false;
571   }
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);
576   }
577   if (!next) {
578     io.GetIoErrorHandler().SignalError("Empty LOGICAL input field");
579     return false;
580   }
581   switch (*next) {
582   case 'T':
583   case 't':
584     x = true;
585     break;
586   case 'F':
587   case 'f':
588     x = false;
589     break;
590   default:
591     io.GetIoErrorHandler().SignalError(
592         "Bad character '%lc' in LOGICAL input field", *next);
593     return false;
594   }
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
599     }
600   }
601   return true;
602 }
603 
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) {
608   bool result{true};
609   while (true) {
610     std::size_t byteCount{0};
611     auto ch{io.GetCurrentChar(byteCount)};
612     if (!ch) {
613       if (io.AdvanceRecord()) {
614         continue;
615       } else {
616         result = false; // EOF in character value
617         break;
618       }
619     }
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);
626       } else {
627         break; // closing delimiter
628       }
629     }
630     if (length > 0) {
631       *x++ = *ch;
632       --length;
633     }
634   }
635   std::fill_n(x, length, ' ');
636   return result;
637 }
638 
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);
647   }
648   if (IsNamelistName(io) || io.GetConnectionState().IsAtEOF()) {
649     return false;
650   }
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
654   // in NextInField.
655   std::optional<int> remaining{length > 0 ? maxUTF8Bytes : 0};
656   while (std::optional<char32_t> next{io.NextInField(remaining, edit)}) {
657     bool isSep{false};
658     switch (*next) {
659     case ' ':
660     case '\t':
661     case '/':
662       isSep = true;
663       break;
664     case ',':
665       isSep = !(edit.modes.editingFlags & decimalComma);
666       break;
667     case ';':
668       isSep = !!(edit.modes.editingFlags & decimalComma);
669       break;
670     default:
671       break;
672     }
673     if (isSep) {
674       remaining = 0;
675     } else {
676       *x++ = *next;
677       remaining = --length > 0 ? maxUTF8Bytes : 0;
678     }
679   }
680   std::fill_n(x, length, ' ');
681   return true;
682 }
683 
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);
690   case 'A':
691   case 'G':
692     break;
693   case 'B':
694     return EditBOZInput<1>(io, edit, x, length * sizeof *x);
695   case 'O':
696     return EditBOZInput<3>(io, edit, x, length * sizeof *x);
697   case 'Z':
698     return EditBOZInput<4>(io, edit, x, length * sizeof *x);
699   default:
700     io.GetIoErrorHandler().SignalError(IostatErrorInFormat,
701         "Data edit descriptor '%c' may not be used with a CHARACTER data item",
702         edit.descriptor);
703     return false;
704   }
705   const ConnectionState &connection{io.GetConnectionState()};
706   std::size_t remaining{length};
707   if (edit.width && *edit.width > 0) {
708     remaining = *edit.width;
709   }
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
712   // trailing padding.
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) {
720     if (ready == 0) {
721       ready = io.GetNextInputBytes(input);
722       if (ready == 0) {
723         if (io.CheckForEndOfRecord()) {
724           std::fill_n(x, length, ' '); // PAD='YES'
725         }
726         return !io.GetIoErrorHandler().InError();
727       }
728     }
729     std::size_t chunk;
730     bool skipping{skip > 0};
731     if (connection.isUTF8) {
732       chunk = MeasureUTF8Bytes(*input);
733       if (skipping) {
734         --skip;
735       } else if (auto ucs{DecodeUTF8(input)}) {
736         *x++ = *ucs;
737         --length;
738       } else if (chunk == 0) {
739         // error recovery: skip bad encoding
740         chunk = 1;
741       }
742       --remaining;
743     } else if constexpr (sizeof *x > 1) {
744       // Read single byte with expansion into multi-byte CHARACTER
745       chunk = 1;
746       if (skipping) {
747         --skip;
748       } else {
749         *x++ = static_cast<unsigned char>(*input);
750         --length;
751       }
752       --remaining;
753     } else { // single bytes -> default CHARACTER
754       if (skipping) {
755         chunk = std::min<std::size_t>(skip, ready);
756         skip -= chunk;
757       } else {
758         chunk = std::min<std::size_t>(remaining, ready);
759         std::memcpy(x, input, chunk);
760         x += chunk;
761         length -= chunk;
762       }
763       remaining -= chunk;
764     }
765     input += chunk;
766     if (!skipping) {
767       io.GotChar(chunk);
768     }
769     io.HandleRelativePosition(chunk);
770     ready -= chunk;
771   }
772   // Pad the remainder of the input variable, if any.
773   std::fill_n(x, length, ' ');
774   return true;
775 }
776 
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 *);
784 
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);
791 
792 } // namespace Fortran::runtime::io
793