Warning: This file is not a C or C++ file. It does not have highlighting.

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
29namespace Fortran::common {
30
31struct 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.
41ENUM_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
49template <typename CHAR = char> class FormatValidator {
50public:
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
62private:
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
155template <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
166template <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
176template <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.
187template <typename CHAR> void FormatValidator<CHAR>::Advance(TokenKind tk) {
188 cursor_ = laCursor_;
189 token_.set_kind(tk);
190}
191
192template <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
453template <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.
463template <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' && *argString_ != 'L') {
475 ReportWarning("Expected '%s' edit descriptor 'w' value"); // C1306
476 }
477 return false;
478}
479
480template <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.
497template <typename CHAR>
498bool 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.
516template <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
543template <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
555template <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

Warning: This file is not a C or C++ file. It does not have highlighting.

source code of flang/include/flang/Common/format.h