1616
1717namespace Fortran ::runtime::io {
1818
19+ // Checks that a list-directed input value has been entirely consumed and
20+ // doesn't contain unparsed characters before the next value separator.
21+ static inline bool IsCharValueSeparator (const DataEdit &edit, char32_t ch) {
22+ char32_t comma{
23+ edit.modes .editingFlags & decimalComma ? char32_t {' ;' } : char32_t {' ,' }};
24+ return ch == ' ' || ch == ' \t ' || ch == ' /' || ch == comma;
25+ }
26+
27+ static inline bool IsListDirectedFieldComplete (
28+ IoStatementState &io, const DataEdit &edit) {
29+ std::size_t byteCount;
30+ if (auto ch{io.GetCurrentChar (byteCount)}) {
31+ return IsCharValueSeparator (edit, *ch);
32+ } else {
33+ return true ; // end of record: ok
34+ }
35+ }
36+
37+ static bool CheckCompleteListDirectedField (
38+ IoStatementState &io, const DataEdit &edit) {
39+ if (edit.IsListDirected ()) {
40+ std::size_t byteCount;
41+ if (auto ch{io.GetCurrentChar (byteCount)}) {
42+ if (IsCharValueSeparator (edit, *ch)) {
43+ return true ;
44+ } else {
45+ const auto &connection{io.GetConnectionState ()};
46+ io.GetIoErrorHandler ().SignalError (IostatBadListDirectedInputSeparator,
47+ " invalid character (0x%x) after list-directed input value, "
48+ " at column %d in record %d" ,
49+ static_cast <unsigned >(*ch),
50+ static_cast <int >(connection.positionInRecord + 1 ),
51+ static_cast <int >(connection.currentRecordNumber ));
52+ return false ;
53+ }
54+ } else {
55+ return true ; // end of record: ok
56+ }
57+ } else {
58+ return true ;
59+ }
60+ }
61+
1962template <int LOG2_BASE>
2063static bool EditBOZInput (
2164 IoStatementState &io, const DataEdit &edit, void *n, std::size_t bytes) {
@@ -89,28 +132,29 @@ static bool EditBOZInput(
89132 *data |= digit << shift;
90133 shift -= LOG2_BASE;
91134 }
92- return true ;
135+ return CheckCompleteListDirectedField (io, edit) ;
93136}
94137
95138static inline char32_t GetDecimalPoint (const DataEdit &edit) {
96139 return edit.modes .editingFlags & decimalComma ? char32_t {' ,' } : char32_t {' .' };
97140}
98141
99- // Prepares input from a field, and consumes the sign, if any.
100- // Returns true if there's a '-' sign.
101- static bool ScanNumericPrefix (IoStatementState &io, const DataEdit &edit,
142+ // Prepares input from a field, and returns the sign, if any, else '\0'.
143+ static char ScanNumericPrefix (IoStatementState &io, const DataEdit &edit,
102144 std::optional<char32_t > &next, std::optional<int > &remaining) {
103145 remaining = io.CueUpInput (edit);
104146 next = io.NextInField (remaining, edit);
105- bool negative{ false };
147+ char sign{ ' \0 ' };
106148 if (next) {
107- negative = *next == ' -' ;
108- if (negative || *next == ' +' ) {
109- io.SkipSpaces (remaining);
149+ if (*next == ' -' || *next == ' +' ) {
150+ sign = *next;
151+ if (!edit.IsListDirected ()) {
152+ io.SkipSpaces (remaining);
153+ }
110154 next = io.NextInField (remaining, edit);
111155 }
112156 }
113- return negative ;
157+ return sign ;
114158}
115159
116160bool EditIntegerInput (
@@ -141,9 +185,9 @@ bool EditIntegerInput(
141185 }
142186 std::optional<int > remaining;
143187 std::optional<char32_t > next;
144- bool negate {ScanNumericPrefix (io, edit, next, remaining)};
188+ char sign {ScanNumericPrefix (io, edit, next, remaining)};
145189 common::UnsignedInt128 value{0 };
146- bool any{negate };
190+ bool any{!!sign };
147191 bool overflow{false };
148192 for (; next; next = io.NextInField (remaining, edit)) {
149193 char32_t ch{*next};
@@ -178,13 +222,13 @@ bool EditIntegerInput(
178222 return false ;
179223 }
180224 auto maxForKind{common::UnsignedInt128{1 } << ((8 * kind) - 1 )};
181- overflow |= value >= maxForKind && (value > maxForKind || !negate );
225+ overflow |= value >= maxForKind && (value > maxForKind || sign != ' - ' );
182226 if (overflow) {
183227 io.GetIoErrorHandler ().SignalError (IostatIntegerInputOverflow,
184228 " Decimal input overflows INTEGER(%d) variable" , kind);
185229 return false ;
186230 }
187- if (negate ) {
231+ if (sign == ' - ' ) {
188232 value = -value;
189233 }
190234 if (any || !io.GetConnectionState ().IsAtEOF ()) {
@@ -212,13 +256,17 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
212256 }
213257 ++got;
214258 }};
215- if (ScanNumericPrefix (io, edit, next, remaining)) {
259+ char sign{ScanNumericPrefix (io, edit, next, remaining)};
260+ if (sign == ' -' ) {
216261 Put (' -' );
217262 }
218263 bool bzMode{(edit.modes .editingFlags & blankZero) != 0 };
219- if (!next || (!bzMode && *next == ' ' )) { // empty/blank field means zero
220- remaining.reset ();
221- if (!io.GetConnectionState ().IsAtEOF ()) {
264+ if (!next || (!bzMode && *next == ' ' )) {
265+ if (!edit.IsListDirected () && !io.GetConnectionState ().IsAtEOF ()) {
266+ // An empty/blank field means zero when not list-directed.
267+ // A fixed-width field containing only a sign is also zero;
268+ // this behavior isn't standard-conforming in F'2023 but it is
269+ // required to pass FCVS.
222270 Put (' 0' );
223271 }
224272 return got;
@@ -286,6 +334,10 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
286334 // the FCVS suite.
287335 Put (' 0' ); // emit at least one digit
288336 }
337+ // In list-directed input, a bad exponent is not consumed.
338+ auto nextBeforeExponent{next};
339+ auto startExponent{io.GetConnectionState ().positionInRecord };
340+ bool hasGoodExponent{false };
289341 if (next &&
290342 (*next == ' e' || *next == ' E' || *next == ' d' || *next == ' D' ||
291343 *next == ' q' || *next == ' Q' )) {
@@ -306,11 +358,13 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
306358 }
307359 for (exponent = 0 ; next; next = io.NextInField (remaining, edit)) {
308360 if (*next >= ' 0' && *next <= ' 9' ) {
361+ hasGoodExponent = true ;
309362 if (exponent < 10000 ) {
310363 exponent = 10 * exponent + *next - ' 0' ;
311364 }
312365 } else if (*next == ' ' || *next == ' \t ' ) {
313366 if (bzMode) {
367+ hasGoodExponent = true ;
314368 exponent = 10 * exponent;
315369 }
316370 } else {
@@ -321,6 +375,11 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
321375 exponent = -exponent;
322376 }
323377 }
378+ if (!hasGoodExponent) {
379+ // There isn't a good exponent; do not consume it.
380+ next = nextBeforeExponent;
381+ io.HandleAbsolutePosition (startExponent);
382+ }
324383 if (decimalPoint) {
325384 exponent += *decimalPoint;
326385 } else {
@@ -339,6 +398,7 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
339398 // input value.
340399 if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) {
341400 if (next && (*next == ' ' || *next == ' \t ' )) {
401+ io.SkipSpaces (remaining);
342402 next = io.NextInField (remaining, edit);
343403 }
344404 if (!next) { // NextInField fails on separators like ')'
@@ -423,19 +483,26 @@ static bool TryFastPathRealInput(
423483 return false ;
424484 }
425485 }
426- for (; p < limit && (*p == ' ' || *p == ' \t ' ); ++p) {
427- }
428486 if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) {
429- // Need to consume a trailing ')' and any white space after
430- if (p >= limit || *p != ' )' ) {
487+ // Need to consume a trailing ')', possibly with leading spaces
488+ for (; p < limit && (*p == ' ' || *p == ' \t ' ); ++p) {
489+ }
490+ if (p < limit && *p == ' )' ) {
491+ ++p;
492+ } else {
493+ return false ;
494+ }
495+ } else if (edit.IsListDirected ()) {
496+ if (p < limit && !IsCharValueSeparator (edit, *p)) {
431497 return false ;
432498 }
433- for (++p; p < limit && (*p == ' ' || *p == ' \t ' ); ++p) {
499+ } else {
500+ for (; p < limit && (*p == ' ' || *p == ' \t ' ); ++p) {
501+ }
502+ if (edit.width && p < str + *edit.width ) {
503+ return false ; // unconverted characters remain in fixed width field
434504 }
435505 }
436- if (edit.width && p < str + *edit.width ) {
437- return false ; // unconverted characters remain in fixed width field
438- }
439506 // Success on the fast path!
440507 *reinterpret_cast <decimal::BinaryFloatingPointNumber<PRECISION> *>(n) =
441508 converted.binary ;
@@ -451,7 +518,7 @@ template <int KIND>
451518bool EditCommonRealInput (IoStatementState &io, const DataEdit &edit, void *n) {
452519 constexpr int binaryPrecision{common::PrecisionOfRealKind (KIND)};
453520 if (TryFastPathRealInput<binaryPrecision>(io, edit, n)) {
454- return true ;
521+ return CheckCompleteListDirectedField (io, edit) ;
455522 }
456523 // Fast path wasn't available or didn't work; go the more general route
457524 static constexpr int maxDigits{
@@ -465,7 +532,11 @@ bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) {
465532 return false ;
466533 }
467534 if (got == 0 ) {
468- io.GetIoErrorHandler ().SignalError (IostatBadRealInput);
535+ const auto &connection{io.GetConnectionState ()};
536+ io.GetIoErrorHandler ().SignalError (IostatBadRealInput,
537+ " Bad real input data at column %d of record %d" ,
538+ static_cast <int >(connection.positionInRecord + 1 ),
539+ static_cast <int >(connection.currentRecordNumber ));
469540 return false ;
470541 }
471542 bool hadExtra{got > maxDigits};
@@ -512,7 +583,11 @@ bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) {
512583 converted.flags | decimal::Inexact);
513584 }
514585 if (*p) { // unprocessed junk after value
515- io.GetIoErrorHandler ().SignalError (IostatBadRealInput);
586+ const auto &connection{io.GetConnectionState ()};
587+ io.GetIoErrorHandler ().SignalError (IostatBadRealInput,
588+ " Trailing characters after real input data at column %d of record %d" ,
589+ static_cast <int >(connection.positionInRecord + 1 ),
590+ static_cast <int >(connection.currentRecordNumber ));
516591 return false ;
517592 }
518593 *reinterpret_cast <decimal::BinaryFloatingPointNumber<binaryPrecision> *>(n) =
@@ -525,7 +600,7 @@ bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) {
525600 }
526601 RaiseFPExceptions (converted.flags );
527602 }
528- return true ;
603+ return CheckCompleteListDirectedField (io, edit) ;
529604}
530605
531606template <int KIND>
@@ -602,13 +677,13 @@ bool EditLogicalInput(IoStatementState &io, const DataEdit &edit, bool &x) {
602677 " Bad character '%lc' in LOGICAL input field" , *next);
603678 return false ;
604679 }
605- if (remaining) { // ignore the rest of the field
680+ if (remaining) { // ignore the rest of a fixed-width field
606681 io.HandleRelativePosition (*remaining);
607682 } else if (edit.descriptor == DataEdit::ListDirected) {
608683 while (io.NextInField (remaining, edit)) { // discard rest of field
609684 }
610685 }
611- return true ;
686+ return CheckCompleteListDirectedField (io, edit) ;
612687}
613688
614689// See 13.10.3.1 paragraphs 7-9 in Fortran 2018
@@ -800,7 +875,7 @@ bool EditCharacterInput(
800875 }
801876 // Pad the remainder of the input variable, if any.
802877 std::fill_n (x, length, ' ' );
803- return true ;
878+ return CheckCompleteListDirectedField (io, edit) ;
804879}
805880
806881template bool EditRealInput<2 >(IoStatementState &, const DataEdit &, void *);
0 commit comments