1 // Copyright (c) 2019, NVIDIA CORPORATION. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #ifndef FORTRAN_COMMON_FORMAT_H_
16 #define FORTRAN_COMMON_FORMAT_H_
17
18 #include "Fortran.h"
19 #include <cstring>
20
21 // Define a FormatValidator class template to validate a format expression
22 // of a given CHAR kind. To enable use in runtime library code as well as
23 // compiler code, the implementation does its own parsing without recourse
24 // to compiler parser machinery, and avoids features that require C++ runtime
25 // library support. A format expression is a pointer to a fixed size
26 // character string, with an explicit length. Class function Check analyzes
27 // the expression for syntax and semantic errors and warnings. When an error
28 // or warning is found, a caller-supplied reporter function is called, which
29 // may request early termination of validation analysis when some threshold
30 // number of errors have been reported. If the context is a READ, WRITE,
31 // or PRINT statement, rather than a FORMAT statement, statement-specific
32 // checks are also done.
33
34 namespace Fortran::common {
35
36 struct FormatMessage {
37 const char *text; // message text; may have one %s argument
38 const char *arg; // optional %s argument value
39 int offset; // offset to message marker
40 int length; // length of message marker
41 bool isError; // vs. warning
42 };
43
44 template<typename CHAR = char> class FormatValidator {
45 public:
46 using Reporter = std::function<bool(const FormatMessage &)>;
47 FormatValidator(const CHAR *format, size_t length, Reporter reporter,
48 IoStmtKind stmt = IoStmtKind::None)
49 : format_{format}, end_{format + length}, reporter_{reporter}, stmt_{stmt},
50 cursor_{format - 1} {
51 CHECK(format);
52 }
53
54 bool Check();
55
56 private:
57 enum class TokenKind {
58 None,
59 A,
60 B,
61 BN,
62 BZ,
63 D,
64 DC,
65 DP,
66 DT,
67 E,
68 EN,
69 ES,
70 EX,
71 F,
72 G,
73 I,
74 L,
75 O,
76 P,
77 RC,
78 RD,
79 RN,
80 RP,
81 RU,
82 RZ,
83 S,
84 SP,
85 SS,
86 T,
87 TL,
88 TR,
89 X,
90 Z,
91 Colon,
92 Slash,
93 Backslash, // nonstandard: inhibit newline on output
94 Dollar, // nonstandard: inhibit newline on output on terminals
95 Star,
96 LParen,
97 RParen,
98 Comma,
99 Point,
100 Sign,
101 UnsignedInteger, // value in integerValue_
102 String, // char-literal-constant or Hollerith constant
103 };
104
105 struct Token {
set_kindToken106 Token &set_kind(TokenKind kind) {
107 kind_ = kind;
108 return *this;
109 }
set_offsetToken110 Token &set_offset(int offset) {
111 offset_ = offset;
112 return *this;
113 }
set_lengthToken114 Token &set_length(int length) {
115 length_ = length;
116 return *this;
117 }
118
kindToken119 TokenKind kind() const { return kind_; }
offsetToken120 int offset() const { return offset_; }
lengthToken121 int length() const { return length_; }
122
IsSetToken123 bool IsSet() { return kind_ != TokenKind::None; }
124
125 private:
126 TokenKind kind_{TokenKind::None};
127 int offset_{0};
128 int length_{1};
129 };
130
ReportWarning(const char * text)131 void ReportWarning(const char *text) { ReportWarning(text, token_); }
132 void ReportWarning(
133 const char *text, Token &token, const char *arg = nullptr) {
134 FormatMessage msg{
135 text, arg ? arg : argString_, token.offset(), token.length(), false};
136 reporterExit_ |= reporter_(msg);
137 }
138
ReportError(const char * text)139 void ReportError(const char *text) { ReportError(text, token_); }
140 void ReportError(const char *text, Token &token, const char *arg = nullptr) {
141 if (suppressMessageCascade_) {
142 return;
143 }
144 formatHasErrors_ = true;
145 suppressMessageCascade_ = true;
146 FormatMessage msg{
147 text, arg ? arg : argString_, token.offset(), token.length(), true};
148 reporterExit_ |= reporter_(msg);
149 }
150
SetLength()151 void SetLength() { SetLength(token_); }
SetLength(Token & token)152 void SetLength(Token &token) {
153 token.set_length(cursor_ - format_ - token.offset() + (cursor_ < end_));
154 }
155
156 CHAR NextChar();
157 CHAR LookAheadChar();
158 void Advance(TokenKind);
159 void NextToken();
160
161 void check_r(bool allowed = true);
162 bool check_w();
163 void check_m();
164 bool check_d();
165 void check_e();
166
167 const CHAR *const format_; // format text
168 const CHAR *const end_; // one-past-last of format_ text
169 Reporter reporter_;
170 IoStmtKind stmt_;
171
172 const CHAR *cursor_{}; // current location in format_
173 const CHAR *laCursor_{}; // lookahead cursor
174 Token token_{}; // current token
175 int64_t integerValue_{-1}; // value of UnsignedInteger token
176 Token knrToken_{}; // k, n, or r UnsignedInteger token
177 int64_t knrValue_{-1}; // -1 ==> not present
178 int64_t wValue_{-1};
179 char argString_[3]{}; // 1-2 character msg arg; usually edit descriptor name
180 bool formatHasErrors_{false};
181 bool unterminatedFormatError_{false};
182 bool suppressMessageCascade_{false};
183 bool reporterExit_{false};
184 };
185
NextChar()186 template<typename CHAR> CHAR FormatValidator<CHAR>::NextChar() {
187 for (++cursor_; cursor_ < end_; ++cursor_) {
188 if (*cursor_ != ' ') {
189 return toupper(*cursor_);
190 }
191 }
192 cursor_ = end_; // don't allow cursor_ > end_
193 return ' ';
194 }
195
LookAheadChar()196 template<typename CHAR> CHAR FormatValidator<CHAR>::LookAheadChar() {
197 for (laCursor_ = cursor_ + 1; laCursor_ < end_; ++laCursor_) {
198 if (*laCursor_ != ' ') {
199 return toupper(*laCursor_);
200 }
201 }
202 laCursor_ = end_; // don't allow laCursor_ > end_
203 return ' ';
204 }
205
206 // After a call to LookAheadChar, set token kind and advance cursor to laCursor.
Advance(TokenKind tk)207 template<typename CHAR> void FormatValidator<CHAR>::Advance(TokenKind tk) {
208 cursor_ = laCursor_;
209 token_.set_kind(tk);
210 }
211
NextToken()212 template<typename CHAR> void FormatValidator<CHAR>::NextToken() {
213 // At entry, cursor_ points before the start of the next token.
214 // At exit, cursor_ points to last CHAR of token_.
215
216 CHAR c{NextChar()};
217 token_.set_kind(TokenKind::None);
218 token_.set_offset(cursor_ - format_);
219 token_.set_length(1);
220 if (c == '_' && integerValue_ >= 0) { // C1305, C1309, C1310, C1312, C1313
221 ReportError("Kind parameter '_' character in format expression");
222 }
223 integerValue_ = -1;
224
225 switch (c) {
226 case '0':
227 case '1':
228 case '2':
229 case '3':
230 case '4':
231 case '5':
232 case '6':
233 case '7':
234 case '8':
235 case '9': {
236 int64_t lastValue;
237 const CHAR *lastCursor;
238 integerValue_ = 0;
239 bool overflow{false};
240 do {
241 lastValue = integerValue_;
242 lastCursor = cursor_;
243 integerValue_ = 10 * integerValue_ + c - '0';
244 if (lastValue > integerValue_) {
245 overflow = true;
246 }
247 c = NextChar();
248 } while (c >= '0' && c <= '9');
249 cursor_ = lastCursor;
250 token_.set_kind(TokenKind::UnsignedInteger);
251 if (overflow) {
252 SetLength();
253 ReportError("Integer overflow in format expression");
254 break;
255 }
256 if (LookAheadChar() != 'H') {
257 break;
258 }
259 // Hollerith constant
260 if (laCursor_ + integerValue_ < end_) {
261 token_.set_kind(TokenKind::String);
262 cursor_ = laCursor_ + integerValue_;
263 } else {
264 token_.set_kind(TokenKind::None);
265 cursor_ = end_;
266 }
267 SetLength();
268 if (stmt_ == IoStmtKind::Read) { // 13.3.2p6
269 ReportError("'H' edit descriptor in READ format expression");
270 } else if (token_.kind() == TokenKind::None) {
271 ReportError("Unterminated 'H' edit descriptor");
272 } else {
273 ReportWarning("Legacy 'H' edit descriptor");
274 }
275 break;
276 }
277 case 'A': token_.set_kind(TokenKind::A); break;
278 case 'B':
279 switch (LookAheadChar()) {
280 case 'N': Advance(TokenKind::BN); break;
281 case 'Z': Advance(TokenKind::BZ); break;
282 default: token_.set_kind(TokenKind::B); break;
283 }
284 break;
285 case 'D':
286 switch (LookAheadChar()) {
287 case 'C': Advance(TokenKind::DC); break;
288 case 'P': Advance(TokenKind::DP); break;
289 case 'T': Advance(TokenKind::DT); break;
290 default: token_.set_kind(TokenKind::D); break;
291 }
292 break;
293 case 'E':
294 switch (LookAheadChar()) {
295 case 'N': Advance(TokenKind::EN); break;
296 case 'S': Advance(TokenKind::ES); break;
297 case 'X': Advance(TokenKind::EX); break;
298 default: token_.set_kind(TokenKind::E); break;
299 }
300 break;
301 case 'F': token_.set_kind(TokenKind::F); break;
302 case 'G': token_.set_kind(TokenKind::G); break;
303 case 'I': token_.set_kind(TokenKind::I); break;
304 case 'L': token_.set_kind(TokenKind::L); break;
305 case 'O': token_.set_kind(TokenKind::O); break;
306 case 'P': token_.set_kind(TokenKind::P); break;
307 case 'R':
308 switch (LookAheadChar()) {
309 case 'C': Advance(TokenKind::RC); break;
310 case 'D': Advance(TokenKind::RD); break;
311 case 'N': Advance(TokenKind::RN); break;
312 case 'P': Advance(TokenKind::RP); break;
313 case 'U': Advance(TokenKind::RU); break;
314 case 'Z': Advance(TokenKind::RZ); break;
315 default: token_.set_kind(TokenKind::None); break;
316 }
317 break;
318 case 'S':
319 switch (LookAheadChar()) {
320 case 'P': Advance(TokenKind::SP); break;
321 case 'S': Advance(TokenKind::SS); break;
322 default: token_.set_kind(TokenKind::S); break;
323 }
324 break;
325 case 'T':
326 switch (LookAheadChar()) {
327 case 'L': Advance(TokenKind::TL); break;
328 case 'R': Advance(TokenKind::TR); break;
329 default: token_.set_kind(TokenKind::T); break;
330 }
331 break;
332 case 'X': token_.set_kind(TokenKind::X); break;
333 case 'Z': token_.set_kind(TokenKind::Z); break;
334 case '-':
335 case '+': token_.set_kind(TokenKind::Sign); break;
336 case '/': token_.set_kind(TokenKind::Slash); break;
337 case '(': token_.set_kind(TokenKind::LParen); break;
338 case ')': token_.set_kind(TokenKind::RParen); break;
339 case '.': token_.set_kind(TokenKind::Point); break;
340 case ':': token_.set_kind(TokenKind::Colon); break;
341 case '\\': token_.set_kind(TokenKind::Backslash); break;
342 case '$': token_.set_kind(TokenKind::Dollar); break;
343 case '*':
344 token_.set_kind(LookAheadChar() == '(' ? TokenKind::Star : TokenKind::None);
345 break;
346 case ',': {
347 token_.set_kind(TokenKind::Comma);
348 CHAR laChar = LookAheadChar();
349 if (laChar == ',') {
350 Advance(TokenKind::Comma);
351 token_.set_offset(cursor_ - format_);
352 ReportError("Unexpected ',' in format expression");
353 } else if (laChar == ')') {
354 ReportError("Unexpected ',' before ')' in format expression");
355 }
356 break;
357 }
358 case '\'':
359 case '"':
360 for (++cursor_; cursor_ < end_; ++cursor_) {
361 if (*cursor_ == c) {
362 if (auto nc{cursor_ + 1}; nc < end_ && *nc != c) {
363 token_.set_kind(TokenKind::String);
364 break;
365 }
366 ++cursor_;
367 }
368 }
369 SetLength();
370 if (stmt_ == IoStmtKind::Read) { // 13.3.2p6
371 ReportError("String edit descriptor in READ format expression");
372 } else if (token_.kind() != TokenKind::String) {
373 ReportError("Unterminated string");
374 }
375 break;
376 default:
377 if (cursor_ >= end_ && !unterminatedFormatError_) {
378 suppressMessageCascade_ = false;
379 ReportError("Unterminated format expression");
380 unterminatedFormatError_ = true;
381 }
382 token_.set_kind(TokenKind::None);
383 break;
384 }
385
386 SetLength();
387 }
388
check_r(bool allowed)389 template<typename CHAR> void FormatValidator<CHAR>::check_r(bool allowed) {
390 if (!allowed && knrValue_ >= 0) {
391 ReportError("Repeat specifier before '%s' edit descriptor", knrToken_);
392 } else if (knrValue_ == 0) {
393 ReportError("'%s' edit descriptor repeat specifier must be positive",
394 knrToken_); // C1304
395 }
396 }
397
398 // Return the predicate "w value is present" to control further processing.
check_w()399 template<typename CHAR> bool FormatValidator<CHAR>::check_w() {
400 if (token_.kind() == TokenKind::UnsignedInteger) {
401 wValue_ = integerValue_;
402 if (wValue_ == 0 &&
403 (*argString_ == 'A' || *argString_ == 'L' ||
404 stmt_ == IoStmtKind::Read)) { // C1306, 13.7.2.1p6
405 ReportError("'%s' edit descriptor 'w' value must be positive");
406 }
407 NextToken();
408 return true;
409 }
410 if (*argString_ != 'A') {
411 ReportWarning("Expected '%s' edit descriptor 'w' value"); // C1306
412 }
413 return false;
414 }
415
check_m()416 template<typename CHAR> void FormatValidator<CHAR>::check_m() {
417 if (token_.kind() != TokenKind::Point) {
418 return;
419 }
420 NextToken();
421 if (token_.kind() != TokenKind::UnsignedInteger) {
422 ReportError("Expected '%s' edit descriptor 'm' value after '.'");
423 return;
424 }
425 if ((stmt_ == IoStmtKind::Print || stmt_ == IoStmtKind::Write) &&
426 wValue_ > 0 && integerValue_ > wValue_) { // 13.7.2.2p5, 13.7.2.4p6
427 ReportError("'%s' edit descriptor 'm' value is greater than 'w' value");
428 }
429 NextToken();
430 }
431
432 // Return the predicate "d value is present" to control further processing.
check_d()433 template<typename CHAR> bool FormatValidator<CHAR>::check_d() {
434 if (token_.kind() != TokenKind::Point) {
435 ReportError("Expected '%s' edit descriptor '.d' value");
436 return false;
437 }
438 NextToken();
439 if (token_.kind() != TokenKind::UnsignedInteger) {
440 ReportError("Expected '%s' edit descriptor 'd' value after '.'");
441 return false;
442 }
443 NextToken();
444 return true;
445 }
446
check_e()447 template<typename CHAR> void FormatValidator<CHAR>::check_e() {
448 if (token_.kind() != TokenKind::E) {
449 return;
450 }
451 NextToken();
452 if (token_.kind() != TokenKind::UnsignedInteger) {
453 ReportError("Expected '%s' edit descriptor 'e' value after 'E'");
454 return;
455 }
456 NextToken();
457 }
458
Check()459 template<typename CHAR> bool FormatValidator<CHAR>::Check() {
460 if (!*format_) {
461 ReportError("Empty format expression");
462 return formatHasErrors_;
463 }
464 NextToken();
465 if (token_.kind() != TokenKind::LParen) {
466 ReportError("Format expression must have an initial '('");
467 return formatHasErrors_;
468 }
469 NextToken();
470
471 int nestLevel{0}; // Outer level ()s are at level 0.
472 Token starToken{}; // unlimited format token
473 bool hasDataEditDesc{false};
474
475 // Subject to error recovery exceptions, a loop iteration processes an
476 // edit descriptor or does list management. The loop terminates when
477 // - a level-0 right paren is processed (format may be valid)
478 // - the end of an incomplete format is reached
479 // - the error reporter requests termination (error threshold reached)
480 while (!reporterExit_) {
481 Token signToken{};
482 knrValue_ = -1; // -1 ==> not present
483 wValue_ = -1;
484 bool commaRequired{true};
485
486 if (token_.kind() == TokenKind::Sign) {
487 signToken = token_;
488 NextToken();
489 }
490 if (token_.kind() == TokenKind::UnsignedInteger) {
491 knrToken_ = token_;
492 knrValue_ = integerValue_;
493 NextToken();
494 }
495 if (signToken.IsSet() && (knrValue_ < 0 || token_.kind() != TokenKind::P)) {
496 argString_[0] = format_[signToken.offset()];
497 argString_[1] = 0;
498 ReportError("Unexpected '%s' in format expression", signToken);
499 }
500 // Default message argument.
501 // Alphabetic edit descriptor names are one or two characters in length.
502 argString_[0] = toupper(format_[token_.offset()]);
503 argString_[1] = token_.length() > 1 ? toupper(*cursor_) : 0;
504 // Process one format edit descriptor or do format list management.
505 switch (token_.kind()) {
506 case TokenKind::A:
507 // R1307 data-edit-desc -> A [w]
508 hasDataEditDesc = true;
509 check_r();
510 NextToken();
511 check_w();
512 break;
513 case TokenKind::B:
514 case TokenKind::I:
515 case TokenKind::O:
516 case TokenKind::Z:
517 // R1307 data-edit-desc -> B w [. m] | I w [. m] | O w [. m] | Z w [. m]
518 hasDataEditDesc = true;
519 check_r();
520 NextToken();
521 if (check_w()) {
522 check_m();
523 }
524 break;
525 case TokenKind::D:
526 case TokenKind::F:
527 // R1307 data-edit-desc -> D w . d | F w . d
528 hasDataEditDesc = true;
529 check_r();
530 NextToken();
531 if (check_w()) {
532 check_d();
533 }
534 break;
535 case TokenKind::E:
536 case TokenKind::EN:
537 case TokenKind::ES:
538 case TokenKind::EX:
539 // R1307 data-edit-desc ->
540 // E w . d [E e] | EN w . d [E e] | ES w . d [E e] | EX w . d [E e]
541 hasDataEditDesc = true;
542 check_r();
543 NextToken();
544 if (check_w() && check_d()) {
545 check_e();
546 }
547 break;
548 case TokenKind::G:
549 // R1307 data-edit-desc -> G w [. d [E e]]
550 hasDataEditDesc = true;
551 check_r();
552 NextToken();
553 if (check_w()) {
554 if (wValue_ > 0) {
555 if (check_d()) { // C1307
556 check_e();
557 }
558 } else if (token_.kind() == TokenKind::Point && check_d() &&
559 token_.kind() == TokenKind::E) {
560 ReportError("Unexpected 'e' in 'G0' edit descriptor"); // C1308
561 NextToken();
562 if (token_.kind() == TokenKind::UnsignedInteger) {
563 NextToken();
564 }
565 }
566 }
567 break;
568 case TokenKind::L:
569 // R1307 data-edit-desc -> L w
570 hasDataEditDesc = true;
571 check_r();
572 NextToken();
573 check_w();
574 break;
575 case TokenKind::DT:
576 // R1307 data-edit-desc -> DT [char-literal-constant] [( v-list )]
577 hasDataEditDesc = true;
578 check_r();
579 NextToken();
580 if (token_.kind() == TokenKind::String) {
581 NextToken();
582 }
583 if (token_.kind() == TokenKind::LParen) {
584 do {
585 NextToken();
586 if (token_.kind() == TokenKind::Sign) {
587 NextToken();
588 }
589 if (token_.kind() != TokenKind::UnsignedInteger) {
590 ReportError(
591 "Expected integer constant in 'DT' edit descriptor v-list");
592 break;
593 }
594 NextToken();
595 } while (token_.kind() == TokenKind::Comma);
596 if (token_.kind() != TokenKind::RParen) {
597 ReportError("Expected ',' or ')' in 'DT' edit descriptor v-list");
598 while (cursor_ < end_ && token_.kind() != TokenKind::RParen) {
599 NextToken();
600 }
601 }
602 NextToken();
603 }
604 break;
605 case TokenKind::String:
606 // R1304 data-edit-desc -> char-string-edit-desc
607 if (knrValue_ >= 0) {
608 ReportError("Repeat specifier before character string edit descriptor",
609 knrToken_);
610 }
611 NextToken();
612 break;
613 case TokenKind::BN:
614 case TokenKind::BZ:
615 case TokenKind::DC:
616 case TokenKind::DP:
617 case TokenKind::RC:
618 case TokenKind::RD:
619 case TokenKind::RN:
620 case TokenKind::RP:
621 case TokenKind::RU:
622 case TokenKind::RZ:
623 case TokenKind::S:
624 case TokenKind::SP:
625 case TokenKind::SS:
626 // R1317 sign-edit-desc -> SS | SP | S
627 // R1318 blank-interp-edit-desc -> BN | BZ
628 // R1319 round-edit-desc -> RU | RD | RZ | RN | RC | RP
629 // R1320 decimal-edit-desc -> DC | DP
630 check_r(false);
631 NextToken();
632 break;
633 case TokenKind::P: {
634 // R1313 control-edit-desc -> k P
635 if (knrValue_ < 0) {
636 ReportError("'P' edit descriptor must have a scale factor");
637 }
638 // Diagnosing C1302 may require multiple token lookahead.
639 // Save current cursor position to enable backup.
640 const CHAR *saveCursor{cursor_};
641 NextToken();
642 if (token_.kind() == TokenKind::UnsignedInteger) {
643 NextToken();
644 }
645 switch (token_.kind()) {
646 case TokenKind::D:
647 case TokenKind::E:
648 case TokenKind::EN:
649 case TokenKind::ES:
650 case TokenKind::EX:
651 case TokenKind::F:
652 case TokenKind::G: commaRequired = false; break;
653 default:;
654 }
655 cursor_ = saveCursor;
656 NextToken();
657 break;
658 }
659 case TokenKind::T:
660 case TokenKind::TL:
661 case TokenKind::TR:
662 // R1315 position-edit-desc -> T n | TL n | TR n
663 check_r(false);
664 NextToken();
665 if (integerValue_ <= 0) { // C1311
666 ReportError("'%s' edit descriptor must have a positive position value");
667 }
668 NextToken();
669 break;
670 case TokenKind::X:
671 // R1315 position-edit-desc -> n X
672 if (knrValue_ == 0) { // C1311
673 ReportError("'X' edit descriptor must have a positive position value",
674 knrToken_);
675 } else if (knrValue_ < 0) {
676 ReportWarning(
677 "'X' edit descriptor must have a positive position value");
678 }
679 NextToken();
680 break;
681 case TokenKind::Colon:
682 // R1313 control-edit-desc -> :
683 check_r(false);
684 commaRequired = false;
685 NextToken();
686 break;
687 case TokenKind::Slash:
688 // R1313 control-edit-desc -> [r] /
689 commaRequired = false;
690 NextToken();
691 break;
692 case TokenKind::Backslash:
693 check_r(false);
694 ReportWarning("Non-standard '\\' edit descriptor");
695 NextToken();
696 break;
697 case TokenKind::Dollar:
698 check_r(false);
699 ReportWarning("Non-standard '$' edit descriptor");
700 NextToken();
701 break;
702 case TokenKind::Star:
703 // NextToken assigns a token kind of Star only if * is followed by (.
704 // So the next token is guaranteed to be LParen.
705 if (nestLevel > 0) {
706 ReportError("Nested unlimited format item list");
707 }
708 starToken = token_;
709 if (knrValue_ >= 0) {
710 ReportError(
711 "Repeat specifier before unlimited format item list", knrToken_);
712 }
713 hasDataEditDesc = false;
714 NextToken();
715 [[fallthrough]];
716 case TokenKind::LParen:
717 if (knrValue_ == 0) {
718 ReportError("List repeat specifier must be positive", knrToken_);
719 }
720 ++nestLevel;
721 break;
722 case TokenKind::RParen:
723 if (knrValue_ >= 0) {
724 ReportError("Unexpected integer constant", knrToken_);
725 }
726 do {
727 if (nestLevel == 0) {
728 // Any characters after level-0 ) are ignored.
729 return formatHasErrors_; // normal exit (may have messages)
730 }
731 if (nestLevel == 1 && starToken.IsSet() && !hasDataEditDesc) {
732 SetLength(starToken);
733 ReportError( // C1303
734 "Unlimited format item list must contain a data edit descriptor",
735 starToken);
736 }
737 --nestLevel;
738 NextToken();
739 } while (token_.kind() == TokenKind::RParen);
740 if (nestLevel == 0 && starToken.IsSet()) {
741 ReportError("Character in format after unlimited format item list");
742 }
743 break;
744 case TokenKind::Comma:
745 if (knrValue_ >= 0) {
746 ReportError("Unexpected integer constant", knrToken_);
747 }
748 if (suppressMessageCascade_ || reporterExit_) {
749 break;
750 }
751 [[fallthrough]];
752 default: ReportError("Unexpected '%s' in format expression"); NextToken();
753 }
754
755 // Process comma separator and exit an incomplete format.
756 switch (token_.kind()) {
757 case TokenKind::Colon: // Comma not required; token not yet processed.
758 case TokenKind::Slash: // Comma not required; token not yet processed.
759 case TokenKind::RParen: // Comma not allowed; token not yet processed.
760 suppressMessageCascade_ = false;
761 break;
762 case TokenKind::LParen: // Comma not allowed; token already processed.
763 case TokenKind::Comma: // Normal comma case; move past token.
764 suppressMessageCascade_ = false;
765 NextToken();
766 break;
767 case TokenKind::Sign: // Error; main switch has a better message.
768 case TokenKind::None: // Error; token not yet processed.
769 if (cursor_ >= end_) {
770 return formatHasErrors_; // incomplete format error exit
771 }
772 break;
773 default:
774 // Possible first token of the next format item; token not yet processed.
775 if (commaRequired) {
776 ReportError("Expected ',' or ')' in format expression"); // C1302
777 }
778 }
779 }
780
781 return formatHasErrors_; // error reporter (message threshold) exit
782 }
783
784 }
785 #endif // FORTRAN_COMMON_FORMAT_H_
786