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