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(bool checkScaleFactor = false);
129   void check_k();
130   void check_e();
131 
132   const CHAR *const format_; // format text
133   const CHAR *const end_; // one-past-last of format_ text
134   Reporter reporter_;
135   IoStmtKind stmt_;
136 
137   const CHAR *cursor_{}; // current location in format_
138   const CHAR *laCursor_{}; // lookahead cursor
139   TokenKind previousTokenKind_{TokenKind::None};
140   Token token_{}; // current token
141   Token knrToken_{}; // k, n, or r UnsignedInteger token
142   Token scaleFactorToken_{}; // most recent scale factor token P
143   int64_t integerValue_{-1}; // value of UnsignedInteger token
144   int64_t knrValue_{-1}; // -1 ==> not present
145   int64_t scaleFactorValue_{}; // signed k in kP
146   int64_t wValue_{-1};
147   char argString_[3]{}; // 1-2 character msg arg; usually edit descriptor name
148   bool formatHasErrors_{false};
149   bool unterminatedFormatError_{false};
150   bool suppressMessageCascade_{false};
151   bool reporterExit_{false};
152   int maxNesting_{0}; // max level of nested parentheses
153 };
154 
IsWhite(CHAR c)155 template <typename CHAR> static inline bool IsWhite(CHAR c) {
156   // White space.  ' ' is standard.  Other characters are extensions.
157   // Extension candidates:
158   //   '\t' (horizontal tab)
159   //   '\n' (new line)
160   //   '\v' (vertical tab)
161   //   '\f' (form feed)
162   //   '\r' (carriage ret)
163   return c == ' ' || c == '\t' || c == '\v';
164 }
165 
NextChar()166 template <typename CHAR> CHAR FormatValidator<CHAR>::NextChar() {
167   for (++cursor_; cursor_ < end_; ++cursor_) {
168     if (!IsWhite(*cursor_)) {
169       return toupper(*cursor_);
170     }
171   }
172   cursor_ = end_; // don't allow cursor_ > end_
173   return ' ';
174 }
175 
LookAheadChar()176 template <typename CHAR> CHAR FormatValidator<CHAR>::LookAheadChar() {
177   for (laCursor_ = cursor_ + 1; laCursor_ < end_; ++laCursor_) {
178     if (!IsWhite(*laCursor_)) {
179       return toupper(*laCursor_);
180     }
181   }
182   laCursor_ = end_; // don't allow laCursor_ > end_
183   return ' ';
184 }
185 
186 // After a call to LookAheadChar, set token kind and advance cursor to laCursor.
Advance(TokenKind tk)187 template <typename CHAR> void FormatValidator<CHAR>::Advance(TokenKind tk) {
188   cursor_ = laCursor_;
189   token_.set_kind(tk);
190 }
191 
NextToken()192 template <typename CHAR> void FormatValidator<CHAR>::NextToken() {
193   // At entry, cursor_ points before the start of the next token.
194   // At exit, cursor_ points to last CHAR of token_.
195 
196   previousTokenKind_ = token_.kind();
197   CHAR c{NextChar()};
198   token_.set_kind(TokenKind::None);
199   token_.set_offset(cursor_ - format_);
200   token_.set_length(1);
201   if (c == '_' && integerValue_ >= 0) { // C1305, C1309, C1310, C1312, C1313
202     ReportError("Kind parameter '_' character in format expression");
203   }
204   integerValue_ = -1;
205 
206   switch (c) {
207   case '0':
208   case '1':
209   case '2':
210   case '3':
211   case '4':
212   case '5':
213   case '6':
214   case '7':
215   case '8':
216   case '9': {
217     int64_t lastValue;
218     const CHAR *lastCursor;
219     integerValue_ = 0;
220     bool overflow{false};
221     do {
222       lastValue = integerValue_;
223       lastCursor = cursor_;
224       integerValue_ = 10 * integerValue_ + c - '0';
225       if (lastValue > integerValue_) {
226         overflow = true;
227       }
228       c = NextChar();
229     } while (c >= '0' && c <= '9');
230     cursor_ = lastCursor;
231     token_.set_kind(TokenKind::UnsignedInteger);
232     if (overflow) {
233       SetLength();
234       ReportError("Integer overflow in format expression");
235       break;
236     }
237     if (LookAheadChar() != 'H') {
238       break;
239     }
240     // Hollerith constant
241     if (laCursor_ + integerValue_ < end_) {
242       token_.set_kind(TokenKind::String);
243       cursor_ = laCursor_ + integerValue_;
244     } else {
245       token_.set_kind(TokenKind::None);
246       cursor_ = end_;
247     }
248     SetLength();
249     if (stmt_ == IoStmtKind::Read) { // 13.3.2p6
250       ReportError("'H' edit descriptor in READ format expression");
251     } else if (token_.kind() == TokenKind::None) {
252       ReportError("Unterminated 'H' edit descriptor");
253     } else {
254       ReportWarning("Legacy 'H' edit descriptor");
255     }
256     break;
257   }
258   case 'A':
259     token_.set_kind(TokenKind::A);
260     break;
261   case 'B':
262     switch (LookAheadChar()) {
263     case 'N':
264       Advance(TokenKind::BN);
265       break;
266     case 'Z':
267       Advance(TokenKind::BZ);
268       break;
269     default:
270       token_.set_kind(TokenKind::B);
271       break;
272     }
273     break;
274   case 'D':
275     switch (LookAheadChar()) {
276     case 'C':
277       Advance(TokenKind::DC);
278       break;
279     case 'P':
280       Advance(TokenKind::DP);
281       break;
282     case 'T':
283       Advance(TokenKind::DT);
284       break;
285     default:
286       token_.set_kind(TokenKind::D);
287       break;
288     }
289     break;
290   case 'E':
291     switch (LookAheadChar()) {
292     case 'N':
293       Advance(TokenKind::EN);
294       break;
295     case 'S':
296       Advance(TokenKind::ES);
297       break;
298     case 'X':
299       Advance(TokenKind::EX);
300       break;
301     default:
302       token_.set_kind(TokenKind::E);
303       break;
304     }
305     break;
306   case 'F':
307     token_.set_kind(TokenKind::F);
308     break;
309   case 'G':
310     token_.set_kind(TokenKind::G);
311     break;
312   case 'I':
313     token_.set_kind(TokenKind::I);
314     break;
315   case 'L':
316     token_.set_kind(TokenKind::L);
317     break;
318   case 'O':
319     token_.set_kind(TokenKind::O);
320     break;
321   case 'P':
322     token_.set_kind(TokenKind::P);
323     break;
324   case 'R':
325     switch (LookAheadChar()) {
326     case 'C':
327       Advance(TokenKind::RC);
328       break;
329     case 'D':
330       Advance(TokenKind::RD);
331       break;
332     case 'N':
333       Advance(TokenKind::RN);
334       break;
335     case 'P':
336       Advance(TokenKind::RP);
337       break;
338     case 'U':
339       Advance(TokenKind::RU);
340       break;
341     case 'Z':
342       Advance(TokenKind::RZ);
343       break;
344     default:
345       token_.set_kind(TokenKind::None);
346       break;
347     }
348     break;
349   case 'S':
350     switch (LookAheadChar()) {
351     case 'P':
352       Advance(TokenKind::SP);
353       break;
354     case 'S':
355       Advance(TokenKind::SS);
356       break;
357     default:
358       token_.set_kind(TokenKind::S);
359       break;
360     }
361     break;
362   case 'T':
363     switch (LookAheadChar()) {
364     case 'L':
365       Advance(TokenKind::TL);
366       break;
367     case 'R':
368       Advance(TokenKind::TR);
369       break;
370     default:
371       token_.set_kind(TokenKind::T);
372       break;
373     }
374     break;
375   case 'X':
376     token_.set_kind(TokenKind::X);
377     break;
378   case 'Z':
379     token_.set_kind(TokenKind::Z);
380     break;
381   case '-':
382   case '+':
383     token_.set_kind(TokenKind::Sign);
384     break;
385   case '/':
386     token_.set_kind(TokenKind::Slash);
387     break;
388   case '(':
389     token_.set_kind(TokenKind::LParen);
390     break;
391   case ')':
392     token_.set_kind(TokenKind::RParen);
393     break;
394   case '.':
395     token_.set_kind(TokenKind::Point);
396     break;
397   case ':':
398     token_.set_kind(TokenKind::Colon);
399     break;
400   case '\\':
401     token_.set_kind(TokenKind::Backslash);
402     break;
403   case '$':
404     token_.set_kind(TokenKind::Dollar);
405     break;
406   case '*':
407     token_.set_kind(LookAheadChar() == '(' ? TokenKind::Star : TokenKind::None);
408     break;
409   case ',': {
410     token_.set_kind(TokenKind::Comma);
411     CHAR laChar = LookAheadChar();
412     if (laChar == ',') {
413       Advance(TokenKind::Comma);
414       token_.set_offset(cursor_ - format_);
415       ReportError("Unexpected ',' in format expression");
416     } else if (laChar == ')') {
417       ReportError("Unexpected ',' before ')' in format expression");
418     }
419     break;
420   }
421   case '\'':
422   case '"':
423     for (++cursor_; cursor_ < end_; ++cursor_) {
424       if (*cursor_ == c) {
425         if (auto nc{cursor_ + 1}; nc < end_ && *nc != c) {
426           token_.set_kind(TokenKind::String);
427           break;
428         }
429         ++cursor_;
430       }
431     }
432     SetLength();
433     if (stmt_ == IoStmtKind::Read &&
434         previousTokenKind_ != TokenKind::DT) { // 13.3.2p6
435       ReportError("String edit descriptor in READ format expression");
436     } else if (token_.kind() != TokenKind::String) {
437       ReportError("Unterminated string");
438     }
439     break;
440   default:
441     if (cursor_ >= end_ && !unterminatedFormatError_) {
442       suppressMessageCascade_ = false;
443       ReportError("Unterminated format expression");
444       unterminatedFormatError_ = true;
445     }
446     token_.set_kind(TokenKind::None);
447     break;
448   }
449 
450   SetLength();
451 }
452 
check_r(bool allowed)453 template <typename CHAR> void FormatValidator<CHAR>::check_r(bool allowed) {
454   if (!allowed && knrValue_ >= 0) {
455     ReportError("Repeat specifier before '%s' edit descriptor", knrToken_);
456   } else if (knrValue_ == 0) {
457     ReportError("'%s' edit descriptor repeat specifier must be positive",
458         knrToken_); // C1304
459   }
460 }
461 
462 // Return the predicate "w value is present" to control further processing.
check_w()463 template <typename CHAR> bool FormatValidator<CHAR>::check_w() {
464   if (token_.kind() == TokenKind::UnsignedInteger) {
465     wValue_ = integerValue_;
466     if (wValue_ == 0 &&
467         (*argString_ == 'A' || *argString_ == 'L' ||
468             stmt_ == IoStmtKind::Read)) { // C1306, 13.7.2.1p6
469       ReportError("'%s' edit descriptor 'w' value must be positive");
470     }
471     NextToken();
472     return true;
473   }
474   if (*argString_ != 'A') {
475     ReportWarning("Expected '%s' edit descriptor 'w' value"); // C1306
476   }
477   return false;
478 }
479 
check_m()480 template <typename CHAR> void FormatValidator<CHAR>::check_m() {
481   if (token_.kind() != TokenKind::Point) {
482     return;
483   }
484   NextToken();
485   if (token_.kind() != TokenKind::UnsignedInteger) {
486     ReportError("Expected '%s' edit descriptor 'm' value after '.'");
487     return;
488   }
489   if ((stmt_ == IoStmtKind::Print || stmt_ == IoStmtKind::Write) &&
490       wValue_ > 0 && integerValue_ > wValue_) { // 13.7.2.2p5, 13.7.2.4p6
491     ReportError("'%s' edit descriptor 'm' value is greater than 'w' value");
492   }
493   NextToken();
494 }
495 
496 // Return the predicate "d value is present" to control further processing.
497 template <typename CHAR>
check_d(bool checkScaleFactor)498 bool FormatValidator<CHAR>::check_d(bool checkScaleFactor) {
499   if (token_.kind() != TokenKind::Point) {
500     ReportError("Expected '%s' edit descriptor '.d' value");
501     return false;
502   }
503   NextToken();
504   if (token_.kind() != TokenKind::UnsignedInteger) {
505     ReportError("Expected '%s' edit descriptor 'd' value after '.'");
506     return false;
507   }
508   if (checkScaleFactor) {
509     check_k();
510   }
511   NextToken();
512   return true;
513 }
514 
515 // Check the value of scale factor k against a field width d.
check_k()516 template <typename CHAR> void FormatValidator<CHAR>::check_k() {
517   // Limit the check to D and E edit descriptors in output statements that
518   // explicitly set the scale factor.
519   if (stmt_ != IoStmtKind::Print && stmt_ != IoStmtKind::Write) {
520     return;
521   }
522   if (!scaleFactorToken_.IsSet()) {
523     return;
524   }
525   // 13.7.2.3.3p5 - The values of d and k must satisfy:
526   //   −d < k <= 0; or
527   //    0 < k < d+2
528   const int64_t d{integerValue_};
529   const int64_t k{scaleFactorValue_};
530   // Exception:  d = k = 0 is nonstandard, but has a reasonable interpretation.
531   if (d == 0 && k == 0) {
532     return;
533   }
534   if (k <= 0 && !(-d < k)) {
535     ReportError("Negative scale factor k (from kP) and width d in a '%s' "
536                 "edit descriptor must satisfy '-d < k'");
537   } else if (k > 0 && !(k < d + 2)) {
538     ReportError("Positive scale factor k (from kP) and width d in a '%s' "
539                 "edit descriptor must satisfy 'k < d+2'");
540   }
541 }
542 
check_e()543 template <typename CHAR> void FormatValidator<CHAR>::check_e() {
544   if (token_.kind() != TokenKind::E) {
545     return;
546   }
547   NextToken();
548   if (token_.kind() != TokenKind::UnsignedInteger) {
549     ReportError("Expected '%s' edit descriptor 'e' value after 'E'");
550     return;
551   }
552   NextToken();
553 }
554 
Check()555 template <typename CHAR> bool FormatValidator<CHAR>::Check() {
556   if (!*format_) {
557     ReportError("Empty format expression");
558     return formatHasErrors_;
559   }
560   NextToken();
561   if (token_.kind() != TokenKind::LParen) {
562     ReportError("Format expression must have an initial '('");
563     return formatHasErrors_;
564   }
565   NextToken();
566 
567   int nestLevel{0}; // Outer level ()s are at level 0.
568   Token starToken{}; // unlimited format token
569   bool hasDataEditDesc{false};
570 
571   // Subject to error recovery exceptions, a loop iteration processes one
572   // edit descriptor or does list management.  The loop terminates when
573   //  - a level-0 right paren is processed (format may be valid)
574   //  - the end of an incomplete format is reached
575   //  - the error reporter requests termination (error threshold reached)
576   while (!reporterExit_) {
577     Token signToken{};
578     knrValue_ = -1; // -1 ==> not present
579     wValue_ = -1;
580     bool commaRequired{true};
581 
582     if (token_.kind() == TokenKind::Sign) {
583       signToken = token_;
584       NextToken();
585     }
586     if (token_.kind() == TokenKind::UnsignedInteger) {
587       knrToken_ = token_;
588       knrValue_ = integerValue_;
589       NextToken();
590     }
591     if (signToken.IsSet() && (knrValue_ < 0 || token_.kind() != TokenKind::P)) {
592       argString_[0] = format_[signToken.offset()];
593       argString_[1] = 0;
594       ReportError("Unexpected '%s' in format expression", signToken);
595     }
596     // Default message argument.
597     // Alphabetic edit descriptor names are one or two characters in length.
598     argString_[0] = toupper(format_[token_.offset()]);
599     argString_[1] = token_.length() > 1 ? toupper(*cursor_) : 0;
600     // Process one format edit descriptor or do format list management.
601     switch (token_.kind()) {
602     case TokenKind::A:
603       // R1307 data-edit-desc -> A [w]
604       hasDataEditDesc = true;
605       check_r();
606       NextToken();
607       check_w();
608       break;
609     case TokenKind::B:
610     case TokenKind::I:
611     case TokenKind::O:
612     case TokenKind::Z:
613       // R1307 data-edit-desc -> B w [. m] | I w [. m] | O w [. m] | Z w [. m]
614       hasDataEditDesc = true;
615       check_r();
616       NextToken();
617       if (check_w()) {
618         check_m();
619       }
620       break;
621     case TokenKind::D:
622     case TokenKind::F: {
623       // R1307 data-edit-desc -> D w . d | F w . d
624       bool isD{token_.kind() == TokenKind::D};
625       hasDataEditDesc = true;
626       check_r();
627       NextToken();
628       if (check_w()) {
629         check_d(/*checkScaleFactor=*/isD);
630       }
631       break;
632     }
633     case TokenKind::E:
634     case TokenKind::EN:
635     case TokenKind::ES:
636     case TokenKind::EX: {
637       // R1307 data-edit-desc ->
638       //   E w . d [E e] | EN w . d [E e] | ES w . d [E e] | EX w . d [E e]
639       bool isE{token_.kind() == TokenKind::E};
640       hasDataEditDesc = true;
641       check_r();
642       NextToken();
643       if (check_w() && check_d(/*checkScaleFactor=*/isE)) {
644         check_e();
645       }
646       break;
647     }
648     case TokenKind::G:
649       // R1307 data-edit-desc -> G w [. d [E e]]
650       hasDataEditDesc = true;
651       check_r();
652       NextToken();
653       if (check_w()) {
654         if (wValue_ > 0) {
655           if (check_d()) { // C1307
656             check_e();
657           }
658         } else if (token_.kind() == TokenKind::Point && check_d() &&
659             token_.kind() == TokenKind::E) { // C1308
660           ReportError("A 'G0' edit descriptor must not have an 'e' value");
661           NextToken();
662           if (token_.kind() == TokenKind::UnsignedInteger) {
663             NextToken();
664           }
665         }
666       }
667       break;
668     case TokenKind::L:
669       // R1307 data-edit-desc -> L w
670       hasDataEditDesc = true;
671       check_r();
672       NextToken();
673       check_w();
674       break;
675     case TokenKind::DT:
676       // R1307 data-edit-desc -> DT [char-literal-constant] [( v-list )]
677       hasDataEditDesc = true;
678       check_r();
679       NextToken();
680       if (token_.kind() == TokenKind::String) {
681         NextToken();
682       }
683       if (token_.kind() == TokenKind::LParen) {
684         do {
685           NextToken();
686           if (token_.kind() == TokenKind::Sign) {
687             NextToken();
688           }
689           if (token_.kind() != TokenKind::UnsignedInteger) {
690             ReportError(
691                 "Expected integer constant in 'DT' edit descriptor v-list");
692             break;
693           }
694           NextToken();
695         } while (token_.kind() == TokenKind::Comma);
696         if (token_.kind() != TokenKind::RParen) {
697           ReportError("Expected ',' or ')' in 'DT' edit descriptor v-list");
698           while (cursor_ < end_ && token_.kind() != TokenKind::RParen) {
699             NextToken();
700           }
701         }
702         NextToken();
703       }
704       break;
705     case TokenKind::String:
706       // R1304 data-edit-desc -> char-string-edit-desc
707       if (knrValue_ >= 0) {
708         ReportError("Repeat specifier before character string edit descriptor",
709             knrToken_);
710       }
711       NextToken();
712       break;
713     case TokenKind::BN:
714     case TokenKind::BZ:
715     case TokenKind::DC:
716     case TokenKind::DP:
717     case TokenKind::RC:
718     case TokenKind::RD:
719     case TokenKind::RN:
720     case TokenKind::RP:
721     case TokenKind::RU:
722     case TokenKind::RZ:
723     case TokenKind::S:
724     case TokenKind::SP:
725     case TokenKind::SS:
726       // R1317 sign-edit-desc -> SS | SP | S
727       // R1318 blank-interp-edit-desc -> BN | BZ
728       // R1319 round-edit-desc -> RU | RD | RZ | RN | RC | RP
729       // R1320 decimal-edit-desc -> DC | DP
730       check_r(false);
731       NextToken();
732       break;
733     case TokenKind::P: {
734       // R1313 control-edit-desc -> k P
735       if (knrValue_ < 0) {
736         ReportError("'P' edit descriptor must have a scale factor");
737       } else {
738         scaleFactorToken_ = knrToken_;
739         if (signToken.IsSet() && format_[signToken.offset()] == '-') {
740           scaleFactorValue_ = -knrValue_;
741         } else {
742           scaleFactorValue_ = knrValue_;
743         }
744       }
745       // Diagnosing C1302 may require multiple token lookahead.
746       // Save current cursor position to enable backup.
747       const CHAR *saveCursor{cursor_};
748       NextToken();
749       if (token_.kind() == TokenKind::UnsignedInteger) {
750         NextToken();
751       }
752       switch (token_.kind()) {
753       case TokenKind::D:
754       case TokenKind::E:
755       case TokenKind::EN:
756       case TokenKind::ES:
757       case TokenKind::EX:
758       case TokenKind::F:
759       case TokenKind::G:
760         commaRequired = false;
761         break;
762       default:;
763       }
764       cursor_ = saveCursor;
765       NextToken();
766       break;
767     }
768     case TokenKind::T:
769     case TokenKind::TL:
770     case TokenKind::TR:
771       // R1315 position-edit-desc -> T n | TL n | TR n
772       check_r(false);
773       NextToken();
774       if (integerValue_ <= 0) { // C1311
775         ReportError("'%s' edit descriptor must have a positive position value");
776       }
777       NextToken();
778       break;
779     case TokenKind::X:
780       // R1315 position-edit-desc -> n X
781       if (knrValue_ == 0) { // C1311
782         ReportError("'X' edit descriptor must have a positive position value",
783             knrToken_);
784       } else if (knrValue_ < 0) {
785         ReportWarning(
786             "'X' edit descriptor must have a positive position value");
787       }
788       NextToken();
789       break;
790     case TokenKind::Colon:
791       // R1313 control-edit-desc -> :
792       check_r(false);
793       commaRequired = false;
794       NextToken();
795       break;
796     case TokenKind::Slash:
797       // R1313 control-edit-desc -> [r] /
798       commaRequired = false;
799       NextToken();
800       break;
801     case TokenKind::Backslash:
802       check_r(false);
803       ReportWarning("Non-standard '\\' edit descriptor");
804       NextToken();
805       break;
806     case TokenKind::Dollar:
807       check_r(false);
808       ReportWarning("Non-standard '$' edit descriptor");
809       NextToken();
810       break;
811     case TokenKind::Star:
812       // NextToken assigns a token kind of Star only if * is followed by (.
813       // So the next token is guaranteed to be LParen.
814       if (nestLevel > 0) {
815         ReportError("Nested unlimited format item list");
816       }
817       starToken = token_;
818       if (knrValue_ >= 0) {
819         ReportError(
820             "Repeat specifier before unlimited format item list", knrToken_);
821       }
822       hasDataEditDesc = false;
823       NextToken();
824       [[fallthrough]];
825     case TokenKind::LParen:
826       if (knrValue_ == 0) {
827         ReportError("List repeat specifier must be positive", knrToken_);
828       }
829       if (++nestLevel > maxNesting_) {
830         maxNesting_ = nestLevel;
831       }
832       break;
833     case TokenKind::RParen:
834       if (knrValue_ >= 0) {
835         ReportError("Unexpected integer constant", knrToken_);
836       }
837       do {
838         if (nestLevel == 0) {
839           // Any characters after level-0 ) are ignored.
840           return formatHasErrors_; // normal exit (may have messages)
841         }
842         if (nestLevel == 1 && starToken.IsSet() && !hasDataEditDesc) {
843           SetLength(starToken);
844           ReportError( // C1303
845               "Unlimited format item list must contain a data edit descriptor",
846               starToken);
847         }
848         --nestLevel;
849         NextToken();
850       } while (token_.kind() == TokenKind::RParen);
851       if (nestLevel == 0 && starToken.IsSet()) {
852         ReportError("Character in format after unlimited format item list");
853       }
854       break;
855     case TokenKind::Comma:
856       if (knrValue_ >= 0) {
857         ReportError("Unexpected integer constant", knrToken_);
858       }
859       if (suppressMessageCascade_ || reporterExit_) {
860         break;
861       }
862       [[fallthrough]];
863     default:
864       ReportError("Unexpected '%s' in format expression");
865       NextToken();
866     }
867 
868     // Process comma separator and exit an incomplete format.
869     switch (token_.kind()) {
870     case TokenKind::Colon: // Comma not required; token not yet processed.
871     case TokenKind::Slash: // Comma not required; token not yet processed.
872     case TokenKind::RParen: // Comma not allowed; token not yet processed.
873       suppressMessageCascade_ = false;
874       break;
875     case TokenKind::LParen: // Comma not allowed; token already processed.
876     case TokenKind::Comma: // Normal comma case; move past token.
877       suppressMessageCascade_ = false;
878       NextToken();
879       break;
880     case TokenKind::Sign: // Error; main switch has a better message.
881     case TokenKind::None: // Error; token not yet processed.
882       if (cursor_ >= end_) {
883         return formatHasErrors_; // incomplete format error exit
884       }
885       break;
886     default:
887       // Possible first token of the next format item; token not yet processed.
888       if (commaRequired) {
889         const char *s{"Expected ',' or ')' in format expression"}; // C1302
890         if (previousTokenKind_ == TokenKind::UnsignedInteger &&
891             itemsWithLeadingInts_.test(token_.kind())) {
892           ReportError(s);
893         } else {
894           ReportWarning(s);
895         }
896       }
897     }
898   }
899 
900   return formatHasErrors_; // error reporter (message threshold) exit
901 }
902 
903 } // namespace Fortran::common
904 #endif // FORTRAN_COMMON_FORMAT_H_
905