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