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