16
16
17
17
namespace Fortran ::runtime::io {
18
18
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
+
19
62
template <int LOG2_BASE>
20
63
static bool EditBOZInput (
21
64
IoStatementState &io, const DataEdit &edit, void *n, std::size_t bytes) {
@@ -89,28 +132,29 @@ static bool EditBOZInput(
89
132
*data |= digit << shift;
90
133
shift -= LOG2_BASE;
91
134
}
92
- return true ;
135
+ return CheckCompleteListDirectedField (io, edit) ;
93
136
}
94
137
95
138
static inline char32_t GetDecimalPoint (const DataEdit &edit) {
96
139
return edit.modes .editingFlags & decimalComma ? char32_t {' ,' } : char32_t {' .' };
97
140
}
98
141
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,
102
144
std::optional<char32_t > &next, std::optional<int > &remaining) {
103
145
remaining = io.CueUpInput (edit);
104
146
next = io.NextInField (remaining, edit);
105
- bool negative{ false };
147
+ char sign{ ' \0 ' };
106
148
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
+ }
110
154
next = io.NextInField (remaining, edit);
111
155
}
112
156
}
113
- return negative ;
157
+ return sign ;
114
158
}
115
159
116
160
bool EditIntegerInput (
@@ -141,9 +185,9 @@ bool EditIntegerInput(
141
185
}
142
186
std::optional<int > remaining;
143
187
std::optional<char32_t > next;
144
- bool negate {ScanNumericPrefix (io, edit, next, remaining)};
188
+ char sign {ScanNumericPrefix (io, edit, next, remaining)};
145
189
common::UnsignedInt128 value{0 };
146
- bool any{negate };
190
+ bool any{!!sign };
147
191
bool overflow{false };
148
192
for (; next; next = io.NextInField (remaining, edit)) {
149
193
char32_t ch{*next};
@@ -178,13 +222,13 @@ bool EditIntegerInput(
178
222
return false ;
179
223
}
180
224
auto maxForKind{common::UnsignedInt128{1 } << ((8 * kind) - 1 )};
181
- overflow |= value >= maxForKind && (value > maxForKind || !negate );
225
+ overflow |= value >= maxForKind && (value > maxForKind || sign != ' - ' );
182
226
if (overflow) {
183
227
io.GetIoErrorHandler ().SignalError (IostatIntegerInputOverflow,
184
228
" Decimal input overflows INTEGER(%d) variable" , kind);
185
229
return false ;
186
230
}
187
- if (negate ) {
231
+ if (sign == ' - ' ) {
188
232
value = -value;
189
233
}
190
234
if (any || !io.GetConnectionState ().IsAtEOF ()) {
@@ -212,13 +256,17 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
212
256
}
213
257
++got;
214
258
}};
215
- if (ScanNumericPrefix (io, edit, next, remaining)) {
259
+ char sign{ScanNumericPrefix (io, edit, next, remaining)};
260
+ if (sign == ' -' ) {
216
261
Put (' -' );
217
262
}
218
263
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.
222
270
Put (' 0' );
223
271
}
224
272
return got;
@@ -286,6 +334,10 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
286
334
// the FCVS suite.
287
335
Put (' 0' ); // emit at least one digit
288
336
}
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 };
289
341
if (next &&
290
342
(*next == ' e' || *next == ' E' || *next == ' d' || *next == ' D' ||
291
343
*next == ' q' || *next == ' Q' )) {
@@ -306,11 +358,13 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
306
358
}
307
359
for (exponent = 0 ; next; next = io.NextInField (remaining, edit)) {
308
360
if (*next >= ' 0' && *next <= ' 9' ) {
361
+ hasGoodExponent = true ;
309
362
if (exponent < 10000 ) {
310
363
exponent = 10 * exponent + *next - ' 0' ;
311
364
}
312
365
} else if (*next == ' ' || *next == ' \t ' ) {
313
366
if (bzMode) {
367
+ hasGoodExponent = true ;
314
368
exponent = 10 * exponent;
315
369
}
316
370
} else {
@@ -321,6 +375,11 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
321
375
exponent = -exponent;
322
376
}
323
377
}
378
+ if (!hasGoodExponent) {
379
+ // There isn't a good exponent; do not consume it.
380
+ next = nextBeforeExponent;
381
+ io.HandleAbsolutePosition (startExponent);
382
+ }
324
383
if (decimalPoint) {
325
384
exponent += *decimalPoint;
326
385
} else {
@@ -339,6 +398,7 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
339
398
// input value.
340
399
if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) {
341
400
if (next && (*next == ' ' || *next == ' \t ' )) {
401
+ io.SkipSpaces (remaining);
342
402
next = io.NextInField (remaining, edit);
343
403
}
344
404
if (!next) { // NextInField fails on separators like ')'
@@ -423,19 +483,26 @@ static bool TryFastPathRealInput(
423
483
return false ;
424
484
}
425
485
}
426
- for (; p < limit && (*p == ' ' || *p == ' \t ' ); ++p) {
427
- }
428
486
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)) {
431
497
return false ;
432
498
}
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
434
504
}
435
505
}
436
- if (edit.width && p < str + *edit.width ) {
437
- return false ; // unconverted characters remain in fixed width field
438
- }
439
506
// Success on the fast path!
440
507
*reinterpret_cast <decimal::BinaryFloatingPointNumber<PRECISION> *>(n) =
441
508
converted.binary ;
@@ -451,7 +518,7 @@ template <int KIND>
451
518
bool EditCommonRealInput (IoStatementState &io, const DataEdit &edit, void *n) {
452
519
constexpr int binaryPrecision{common::PrecisionOfRealKind (KIND)};
453
520
if (TryFastPathRealInput<binaryPrecision>(io, edit, n)) {
454
- return true ;
521
+ return CheckCompleteListDirectedField (io, edit) ;
455
522
}
456
523
// Fast path wasn't available or didn't work; go the more general route
457
524
static constexpr int maxDigits{
@@ -465,7 +532,11 @@ bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) {
465
532
return false ;
466
533
}
467
534
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 ));
469
540
return false ;
470
541
}
471
542
bool hadExtra{got > maxDigits};
@@ -512,7 +583,11 @@ bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) {
512
583
converted.flags | decimal::Inexact);
513
584
}
514
585
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 ));
516
591
return false ;
517
592
}
518
593
*reinterpret_cast <decimal::BinaryFloatingPointNumber<binaryPrecision> *>(n) =
@@ -525,7 +600,7 @@ bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) {
525
600
}
526
601
RaiseFPExceptions (converted.flags );
527
602
}
528
- return true ;
603
+ return CheckCompleteListDirectedField (io, edit) ;
529
604
}
530
605
531
606
template <int KIND>
@@ -602,13 +677,13 @@ bool EditLogicalInput(IoStatementState &io, const DataEdit &edit, bool &x) {
602
677
" Bad character '%lc' in LOGICAL input field" , *next);
603
678
return false ;
604
679
}
605
- if (remaining) { // ignore the rest of the field
680
+ if (remaining) { // ignore the rest of a fixed-width field
606
681
io.HandleRelativePosition (*remaining);
607
682
} else if (edit.descriptor == DataEdit::ListDirected) {
608
683
while (io.NextInField (remaining, edit)) { // discard rest of field
609
684
}
610
685
}
611
- return true ;
686
+ return CheckCompleteListDirectedField (io, edit) ;
612
687
}
613
688
614
689
// See 13.10.3.1 paragraphs 7-9 in Fortran 2018
@@ -800,7 +875,7 @@ bool EditCharacterInput(
800
875
}
801
876
// Pad the remainder of the input variable, if any.
802
877
std::fill_n (x, length, ' ' );
803
- return true ;
878
+ return CheckCompleteListDirectedField (io, edit) ;
804
879
}
805
880
806
881
template bool EditRealInput<2 >(IoStatementState &, const DataEdit &, void *);
0 commit comments