FLANG

format.h
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.
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
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
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.
173 template <typename CHAR> void FormatValidator<CHAR>::Advance(TokenKind tk) {
174 cursor_ = laCursor_;
175 token_.set_kind(tk);
176 }
177
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
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.
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
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.
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
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
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_
Fortran::common::FormatMessage
Definition: format.h:31
IntrinsicLibrary
Definition: IntrinsicCall.cpp:172