FLANG
token-parsers.h
1//===-- lib/Parser/token-parsers.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_PARSER_TOKEN_PARSERS_H_
10#define FORTRAN_PARSER_TOKEN_PARSERS_H_
11
12// These parsers are driven by the parsers of the Fortran grammar to consume
13// the prescanned character stream and recognize context-sensitive tokens.
14
15#include "basic-parsers.h"
16#include "type-parsers.h"
17#include "flang/Parser/char-set.h"
18#include "flang/Parser/characters.h"
19#include "flang/Parser/instrumented-parser.h"
20#include <cstddef>
21#include <cstring>
22#include <limits>
23#include <list>
24#include <optional>
25#include <string>
26
27namespace Fortran::parser {
28
29// "xyz"_ch matches one instance of the characters x, y, or z without skipping
30// any spaces before or after. The parser returns the location of the character
31// on success.
32class AnyOfChars {
33public:
34 using resultType = const char *;
35 constexpr AnyOfChars(const AnyOfChars &) = default;
36 constexpr AnyOfChars(SetOfChars set) : set_{set} {}
37 std::optional<const char *> Parse(ParseState &state) const {
38 if (std::optional<const char *> at{state.PeekAtNextChar()}) {
39 if (set_.Has(**at)) {
40 state.UncheckedAdvance();
41 state.set_anyTokenMatched();
42 return at;
43 }
44 }
45 state.Say(MessageExpectedText{set_});
46 return std::nullopt;
47 }
48
49private:
50 const SetOfChars set_;
51};
52
53constexpr AnyOfChars operator""_ch(const char str[], std::size_t n) {
54 return AnyOfChars{SetOfChars(str, n)};
55}
56
57constexpr auto letter{"abcdefghijklmnopqrstuvwxyz"_ch};
58constexpr auto digit{"0123456789"_ch};
59
60// Skips over optional spaces. Always succeeds.
61struct Space {
62 using resultType = Success;
63 constexpr Space() {}
64 static std::optional<Success> Parse(ParseState &state) {
65 while (std::optional<const char *> p{state.PeekAtNextChar()}) {
66 if (**p != ' ') {
67 break;
68 }
69 state.UncheckedAdvance();
70 }
71 return {Success{}};
72 }
73};
74constexpr Space space;
75
76// Skips a space that in free form requires a warning if it precedes a
77// character that could begin an identifier or keyword. Always succeeds.
78inline void MissingSpace(ParseState &state) {
79 if (!state.inFixedForm()) {
80 state.Nonstandard(
81 LanguageFeature::OptionalFreeFormSpace, "missing space"_port_en_US);
82 }
83}
84
85struct SpaceCheck {
86 using resultType = Success;
87 constexpr SpaceCheck() {}
88 static std::optional<Success> Parse(ParseState &state) {
89 if (std::optional<const char *> p{state.PeekAtNextChar()}) {
90 char ch{**p};
91 if (ch == ' ') {
92 state.UncheckedAdvance();
93 return space.Parse(state);
94 }
95 if (IsLegalInIdentifier(ch)) {
96 MissingSpace(state);
97 }
98 }
99 return {Success{}};
100 }
101};
102constexpr SpaceCheck spaceCheck;
103
104// Matches a token string. Spaces in the token string denote where
105// spaces may appear in the source; they can be made mandatory for
106// some free form keyword sequences. Missing mandatory spaces in free
107// form elicit a warning; they are not necessary for recognition.
108// Spaces before and after the token are also skipped.
109//
110// Token strings appear in the grammar as C++ user-defined literals
111// like "BIND ( C )"_tok and "SYNC ALL"_sptok. The _tok suffix is implied
112// when a string literal appears before the sequencing operator >> or
113// after the sequencing operator /. The literal "..."_id parses a
114// token that cannot be a prefix of a longer identifier.
115template <bool MandatoryFreeFormSpace = false, bool MustBeComplete = false>
116class TokenStringMatch {
117public:
118 using resultType = Success;
119 constexpr TokenStringMatch(const TokenStringMatch &) = default;
120 constexpr TokenStringMatch(const char *str, std::size_t n)
121 : str_{str}, bytes_{n} {}
122 explicit constexpr TokenStringMatch(const char *str) : str_{str} {}
123 std::optional<Success> Parse(ParseState &state) const {
124 space.Parse(state);
125 const char *start{state.GetLocation()};
126 const char *p{str_};
127 std::optional<const char *> at; // initially empty
128 for (std::size_t j{0}; j < bytes_ && *p != '\0'; ++j, ++p) {
129 bool spaceSkipping{*p == ' '};
130 if (spaceSkipping) {
131 if (j + 1 == bytes_ || p[1] == ' ' || p[1] == '\0') {
132 continue; // redundant; ignore
133 }
134 }
135 if (!at) {
136 at = nextCh.Parse(state);
137 if (!at) {
138 return std::nullopt;
139 }
140 }
141 if (spaceSkipping) {
142 if (**at == ' ') {
143 at = nextCh.Parse(state);
144 if (!at) {
145 return std::nullopt;
146 }
147 } else if constexpr (MandatoryFreeFormSpace) {
148 MissingSpace(state);
149 }
150 // 'at' remains full for next iteration
151 } else if (**at == ToLowerCaseLetter(*p)) {
152 at.reset();
153 } else {
154 state.Say(start, MessageExpectedText{str_, bytes_});
155 return std::nullopt;
156 }
157 }
158 if constexpr (MustBeComplete) {
159 if (auto after{state.PeekAtNextChar()}) {
160 if (IsLegalInIdentifier(**after)) {
161 state.Say(start, MessageExpectedText{str_, bytes_});
162 return std::nullopt;
163 }
164 }
165 }
166 state.set_anyTokenMatched();
167 if (IsLegalInIdentifier(p[-1])) {
168 return spaceCheck.Parse(state);
169 } else {
170 return space.Parse(state);
171 }
172 }
173
174private:
175 const char *const str_;
176 const std::size_t bytes_{std::string::npos};
177};
178
179constexpr TokenStringMatch<> operator""_tok(const char str[], std::size_t n) {
180 return {str, n};
181}
182
183constexpr TokenStringMatch<true> operator""_sptok(
184 const char str[], std::size_t n) {
185 return {str, n};
186}
187
188constexpr TokenStringMatch<false, true> operator""_id(
189 const char str[], std::size_t n) {
190 return {str, n};
191}
192
193template <class PA>
194inline constexpr std::enable_if_t<std::is_class_v<PA>,
196operator>>(const char *str, const PA &p) {
198}
199
200template <class PA>
201inline constexpr std::enable_if_t<std::is_class_v<PA>,
203operator/(const PA &p, const char *str) {
205}
206
207template <class PA> inline constexpr auto parenthesized(const PA &p) {
208 return "(" >> p / ")";
209}
210
211template <class PA> inline constexpr auto bracketed(const PA &p) {
212 return "[" >> p / "]";
213}
214
215template <class PA> inline constexpr auto braced(const PA &p) {
216 return "{" >> p / "}";
217}
218
219// Quoted character literal constants.
221 using resultType = std::pair<char, bool /* was escaped */>;
222 static std::optional<resultType> Parse(ParseState &state) {
223 auto at{state.GetLocation()};
224 if (std::optional<const char *> cp{nextCh.Parse(state)}) {
225 char ch{**cp};
226 if (ch == '\n') {
227 state.Say(CharBlock{at, state.GetLocation()},
228 "Unclosed character constant"_err_en_US);
229 return std::nullopt;
230 }
231 if (ch == '\\') {
232 // Most escape sequences in character literals are processed later,
233 // but we have to look for quotes here so that doubled quotes work.
234 if (std::optional<const char *> next{state.PeekAtNextChar()}) {
235 char escaped{**next};
236 if (escaped == '\'' || escaped == '"' || escaped == '\\') {
237 state.UncheckedAdvance();
238 return std::make_pair(escaped, true);
239 }
240 }
241 }
242 return std::make_pair(ch, false);
243 }
244 return std::nullopt;
245 }
246};
247
248template <char quote> struct CharLiteral {
249 using resultType = std::string;
250 static std::optional<std::string> Parse(ParseState &state) {
251 std::string str;
252 static constexpr auto nextch{attempt(CharLiteralChar{})};
253 while (auto ch{nextch.Parse(state)}) {
254 if (ch->second) {
255 str += '\\';
256 } else if (ch->first == quote) {
257 static constexpr auto doubled{attempt(AnyOfChars{SetOfChars{quote}})};
258 if (!doubled.Parse(state)) {
259 return str;
260 }
261 }
262 str += ch->first;
263 }
264 return std::nullopt;
265 }
266};
267
268// Parse "BOZ" binary literal quoted constants.
269// As extensions, support X as an alternate hexadecimal marker, and allow
270// BOZX markers to appear as suffixes.
272 using resultType = std::string;
273 static std::optional<resultType> Parse(ParseState &state) {
274 char base{'\0'};
275 auto baseChar{[&base](char ch) -> bool {
276 switch (ch) {
277 case 'b':
278 case 'o':
279 case 'z':
280 base = ch;
281 return true;
282 case 'x':
283 base = 'z';
284 return true;
285 default:
286 return false;
287 }
288 }};
289
290 space.Parse(state);
291 const char *start{state.GetLocation()};
292 std::optional<const char *> at{nextCh.Parse(state)};
293 if (!at) {
294 return std::nullopt;
295 }
296 if (**at == 'x' &&
297 !state.IsNonstandardOk(LanguageFeature::BOZExtensions,
298 "nonstandard BOZ literal"_port_en_US)) {
299 return std::nullopt;
300 }
301 if (baseChar(**at)) {
302 at = nextCh.Parse(state);
303 if (!at) {
304 return std::nullopt;
305 }
306 }
307
308 char quote = **at;
309 if (quote != '\'' && quote != '"') {
310 return std::nullopt;
311 }
312
313 std::string content;
314 while (true) {
315 at = nextCh.Parse(state);
316 if (!at) {
317 return std::nullopt;
318 }
319 if (**at == quote) {
320 break;
321 }
322 if (**at == ' ') {
323 continue;
324 }
325 if (!IsHexadecimalDigit(**at)) {
326 return std::nullopt;
327 }
328 content += ToLowerCaseLetter(**at);
329 }
330
331 if (!base) {
332 // extension: base allowed to appear as suffix, too
333 if (!(at = nextCh.Parse(state)) || !baseChar(**at) ||
334 !state.IsNonstandardOk(LanguageFeature::BOZExtensions,
335 "nonstandard BOZ literal"_port_en_US)) {
336 return std::nullopt;
337 }
338 spaceCheck.Parse(state);
339 }
340
341 if (content.empty()) {
342 state.Say(start, "no digit in BOZ literal"_err_en_US);
343 return std::nullopt;
344 }
345 return {std::string{base} + '"' + content + '"'};
346 }
347};
348
349// R711 digit-string -> digit [digit]...
350// N.B. not a token -- no space is skipped
352 using resultType = CharBlock;
353 static std::optional<resultType> Parse(ParseState &state) {
354 if (std::optional<const char *> ch1{state.PeekAtNextChar()}) {
355 if (IsDecimalDigit(**ch1)) {
356 state.UncheckedAdvance();
357 while (std::optional<const char *> p{state.PeekAtNextChar()}) {
358 if (!IsDecimalDigit(**p)) {
359 break;
360 }
361 state.UncheckedAdvance();
362 }
363 return CharBlock{*ch1, state.GetLocation()};
364 }
365 }
366 return std::nullopt;
367 }
368};
369constexpr DigitString digitString;
370
372 using resultType = CharBlock;
373 static std::optional<resultType> Parse(ParseState &state) {
374 resultType result{state.GetLocation()};
375 static constexpr auto sign{maybe("+-"_ch / space)};
376 if (sign.Parse(state)) {
377 if (auto digits{digitString.Parse(state)}) {
378 result.ExtendToCover(*digits);
379 return result;
380 }
381 }
382 return std::nullopt;
383 }
384};
385
387 using resultType = std::uint64_t;
388 static std::optional<std::uint64_t> Parse(ParseState &state) {
389 std::optional<const char *> firstDigit{digit.Parse(state)};
390 if (!firstDigit) {
391 return std::nullopt;
392 }
393 std::uint64_t value = **firstDigit - '0';
394 bool overflow{false};
395 static constexpr auto getDigit{attempt(digit)};
396 while (auto nextDigit{getDigit.Parse(state)}) {
397 if (value > std::numeric_limits<std::uint64_t>::max() / 10) {
398 overflow = true;
399 }
400 value *= 10;
401 int digitValue = **nextDigit - '0';
402 if (value > std::numeric_limits<std::uint64_t>::max() - digitValue) {
403 overflow = true;
404 }
405 value += digitValue;
406 }
407 if (overflow) {
408 state.Say(*firstDigit, "overflow in decimal literal"_err_en_US);
409 }
410 return {value};
411 }
412};
413constexpr DigitString64 digitString64;
414
415// R707 signed-int-literal-constant -> [sign] int-literal-constant
416// N.B. Spaces are consumed before and after the sign, since the sign
417// and the int-literal-constant are distinct tokens. Does not
418// handle a trailing kind parameter.
419static std::optional<std::int64_t> SignedInteger(
420 const std::optional<std::uint64_t> &x, Location at, bool negate,
421 ParseState &state) {
422 if (!x) {
423 return std::nullopt;
424 }
425 std::uint64_t limit{std::numeric_limits<std::int64_t>::max()};
426 if (negate) {
427 limit = -(limit + 1);
428 }
429 if (*x > limit) {
430 state.Say(at, "overflow in signed decimal literal"_err_en_US);
431 }
432 std::int64_t value = *x;
433 return std::make_optional<std::int64_t>(negate ? -value : value);
434}
435
436// R710 signed-digit-string -> [sign] digit-string
437// N.B. Not a complete token -- no space is skipped.
438// Used only in the exponent parts of real literal constants.
440 using resultType = std::int64_t;
441 static std::optional<std::int64_t> Parse(ParseState &state) {
442 std::optional<const char *> sign{state.PeekAtNextChar()};
443 if (!sign) {
444 return std::nullopt;
445 }
446 bool negate{**sign == '-'};
447 if (negate || **sign == '+') {
448 state.UncheckedAdvance();
449 }
450 return SignedInteger(digitString64.Parse(state), *sign, negate, state);
451 }
452};
453
454// Variants of the above for use in FORMAT specifications, where spaces
455// must be ignored.
457 using resultType = std::uint64_t;
458 static std::optional<std::uint64_t> Parse(ParseState &state) {
459 static constexpr auto getFirstDigit{space >> digit};
460 std::optional<const char *> firstDigit{getFirstDigit.Parse(state)};
461 if (!firstDigit) {
462 return std::nullopt;
463 }
464 std::uint64_t value = **firstDigit - '0';
465 bool overflow{false};
466 static constexpr auto getDigit{space >> attempt(digit)};
467 while (auto nextDigit{getDigit.Parse(state)}) {
468 if (value > std::numeric_limits<std::uint64_t>::max() / 10) {
469 overflow = true;
470 }
471 value *= 10;
472 int digitValue = **nextDigit - '0';
473 if (value > std::numeric_limits<std::uint64_t>::max() - digitValue) {
474 overflow = true;
475 }
476 value += digitValue;
477 }
478 if (overflow) {
479 state.Say(*firstDigit, "overflow in decimal literal"_err_en_US);
480 }
481 return value;
482 }
483};
484
486 using resultType = std::int64_t;
487 static std::optional<std::int64_t> Parse(ParseState &state) {
488 Location at{state.GetLocation()};
489 return SignedInteger(
490 DigitStringIgnoreSpaces{}.Parse(state), at, false /*positive*/, state);
491 }
492};
493
495 using resultType = std::int64_t;
496 static std::optional<std::int64_t> Parse(ParseState &state) {
497 static constexpr auto getSign{space >> attempt("+-"_ch)};
498 bool negate{false};
499 if (std::optional<const char *> sign{getSign.Parse(state)}) {
500 negate = **sign == '-';
501 }
502 Location at{state.GetLocation()};
503 return SignedInteger(
504 DigitStringIgnoreSpaces{}.Parse(state), at, negate, state);
505 }
506};
507
508// Legacy feature: Hollerith literal constants
510 using resultType = std::string;
511 static std::optional<std::string> Parse(ParseState &state) {
512 space.Parse(state);
513 const char *start{state.GetLocation()};
514 std::optional<std::uint64_t> charCount{
515 DigitStringIgnoreSpaces{}.Parse(state)};
516 if (!charCount || *charCount < 1) {
517 return std::nullopt;
518 }
519 static constexpr auto letterH{"h"_ch};
520 std::optional<const char *> h{letterH.Parse(state)};
521 if (!h) {
522 return std::nullopt;
523 }
524 std::string content;
525 for (auto j{*charCount}; j-- > 0;) {
526 int chBytes{UTF_8CharacterBytes(state.GetLocation())};
527 for (int bytes{chBytes}; bytes > 0; --bytes) {
528 if (std::optional<const char *> at{nextCh.Parse(state)}) {
529 if (chBytes == 1 && !IsPrintable(**at)) {
530 state.Say(start, "Bad character in Hollerith"_err_en_US);
531 return std::nullopt;
532 }
533 content += **at;
534 } else {
535 state.Say(start, "Insufficient characters in Hollerith"_err_en_US);
536 return std::nullopt;
537 }
538 }
539 }
540 return content;
541 }
542};
543
544struct ConsumedAllInputParser {
545 using resultType = Success;
546 constexpr ConsumedAllInputParser() {}
547 static inline std::optional<Success> Parse(ParseState &state) {
548 if (state.IsAtEnd()) {
549 return {Success{}};
550 }
551 return std::nullopt;
552 }
553};
554constexpr ConsumedAllInputParser consumedAllInput;
555
556template <char goal> struct SkipPast {
557 using resultType = Success;
558 constexpr SkipPast() {}
559 constexpr SkipPast(const SkipPast &) {}
560 static std::optional<Success> Parse(ParseState &state) {
561 while (std::optional<const char *> p{state.GetNextChar()}) {
562 if (**p == goal) {
563 return {Success{}};
564 } else if (**p == '\n') {
565 break;
566 }
567 }
568 return std::nullopt;
569 }
570};
571
572template <char goal> struct SkipTo {
573 using resultType = Success;
574 constexpr SkipTo() {}
575 constexpr SkipTo(const SkipTo &) {}
576 static std::optional<Success> Parse(ParseState &state) {
577 while (std::optional<const char *> p{state.PeekAtNextChar()}) {
578 if (**p == goal) {
579 return {Success{}};
580 } else if (**p == '\n') {
581 break;
582 } else {
583 state.UncheckedAdvance();
584 }
585 }
586 return std::nullopt;
587 }
588};
589
590template <char left, char right> struct SkipPastNested {
591 using resultType = Success;
592 constexpr SkipPastNested() {}
593 constexpr SkipPastNested(const SkipPastNested &) {}
594 static std::optional<Success> Parse(ParseState &state) {
595 int nesting{1};
596 while (std::optional<const char *> p{state.GetNextChar()}) {
597 if (**p == right) {
598 if (!--nesting) {
599 return {Success{}};
600 }
601 } else if (**p == left) {
602 ++nesting;
603 } else if (**p == '\n') {
604 break;
605 }
606 }
607 return std::nullopt;
608 }
609};
610
611// A common idiom in the Fortran grammar is an optional item (usually
612// a nonempty comma-separated list) that, if present, must follow a comma
613// and precede a doubled colon. When the item is absent, the comma must
614// not appear, and the doubled colons are optional.
615// [[, xyz] ::] is optionalBeforeColons(xyz)
616// [[, xyz]... ::] is optionalBeforeColons(nonemptyList(xyz))
617template <typename PA> inline constexpr auto optionalBeforeColons(const PA &p) {
618 using resultType = std::optional<typename PA::resultType>;
619 return "," >> construct<resultType>(p) / "::" ||
620 ("::"_tok || !","_tok) >> pure<resultType>();
621}
622template <typename PA>
623inline constexpr auto optionalListBeforeColons(const PA &p) {
624 using resultType = std::list<typename PA::resultType>;
625 return "," >> nonemptyList(p) / "::" ||
626 ("::"_tok || !","_tok) >> pure<resultType>();
627}
628
629// Skip over empty lines, leading spaces, and some compiler directives (viz.,
630// the ones that specify the source form) that might appear before the
631// next statement. Skip over empty statements (bare semicolons) when
632// not in strict standard conformance mode. Always succeeds.
634 using resultType = Success;
635 static std::optional<Success> Parse(ParseState &state) {
636 if (UserState * ustate{state.userState()}) {
637 if (ParsingLog * log{ustate->log()}) {
638 // Save memory: vacate the parsing log before each statement unless
639 // we're logging the whole parse for debugging.
640 if (!ustate->instrumentedParse()) {
641 log->clear();
642 }
643 }
644 }
645 while (std::optional<const char *> at{state.PeekAtNextChar()}) {
646 if (**at == '\n' || **at == ' ') {
647 state.UncheckedAdvance();
648 } else if (**at == '!') {
649 static const char fixed[] = "!dir$ fixed\n", free[] = "!dir$ free\n";
650 static constexpr std::size_t fixedBytes{sizeof fixed - 1};
651 static constexpr std::size_t freeBytes{sizeof free - 1};
652 std::size_t remain{state.BytesRemaining()};
653 if (remain >= fixedBytes && std::memcmp(*at, fixed, fixedBytes) == 0) {
654 state.set_inFixedForm(true).UncheckedAdvance(fixedBytes);
655 } else if (remain >= freeBytes &&
656 std::memcmp(*at, free, freeBytes) == 0) {
657 state.set_inFixedForm(false).UncheckedAdvance(freeBytes);
658 } else {
659 break;
660 }
661 } else if (**at == ';' &&
662 state.IsNonstandardOk(
663 LanguageFeature::EmptyStatement, "empty statement"_port_en_US)) {
664 state.UncheckedAdvance();
665 } else {
666 break;
667 }
668 }
669 return {Success{}};
670 }
671};
672constexpr SkipStuffBeforeStatement skipStuffBeforeStatement;
673
674// R602 underscore -> _
675constexpr auto underscore{"_"_ch};
676
677// Characters besides letters and digits that may appear in names.
678// N.B. Don't accept an underscore if it is immediately followed by a
679// quotation mark, so that kindParam_"character literal" is parsed properly.
680// PGI and ifort accept '$' in identifiers, even as the initial character.
681// Cray and gfortran accept '$', but not as the first character.
682// Cray accepts '@' as well.
683constexpr auto otherIdChar{underscore / !"'\""_ch ||
684 extension<LanguageFeature::PunctuationInNames>(
685 "nonstandard usage: punctuation in name"_port_en_US, "$@"_ch)};
686
687constexpr auto logicalTRUE{
688 (".TRUE."_tok ||
689 extension<LanguageFeature::LogicalAbbreviations>(
690 "nonstandard usage: .T. spelling of .TRUE."_port_en_US,
691 ".T."_tok)) >>
692 pure(true)};
693constexpr auto logicalFALSE{
694 (".FALSE."_tok ||
695 extension<LanguageFeature::LogicalAbbreviations>(
696 "nonstandard usage: .F. spelling of .FALSE."_port_en_US,
697 ".F."_tok)) >>
698 pure(false)};
699
700// deprecated: Hollerith literals
701constexpr auto rawHollerithLiteral{
702 deprecated<LanguageFeature::Hollerith>(HollerithLiteral{})};
703
704template <typename A> constexpr decltype(auto) verbatim(A x) {
705 return sourced(construct<Verbatim>(x));
706}
707
708} // namespace Fortran::parser
709#endif // FORTRAN_PARSER_TOKEN_PARSERS_H_
Definition token-parsers.h:32
Definition char-block.h:26
Definition basic-parsers.h:279
Definition message.h:172
Definition parse-state.h:31
Definition instrumented-parser.h:25
Definition basic-parsers.h:256
Definition user-state.h:32
Definition token-parsers.h:116
Definition user-state.h:34
Definition check-expression.h:19
Definition token-parsers.h:271
Definition token-parsers.h:220
Definition token-parsers.h:248
Definition token-parsers.h:544
Definition token-parsers.h:386
Definition token-parsers.h:456
Definition token-parsers.h:351
Definition token-parsers.h:509
Definition char-set.h:23
Definition token-parsers.h:439
Definition token-parsers.h:633
Definition token-parsers.h:85
Definition token-parsers.h:61