brintos

brintos / llvm-project-archived public Read only

0
0
Text · 22.3 KiB · 0b6e21e Raw
542 lines · cpp
1//===-- lib/Parser/expr-parsers.cpp ---------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9// Per-type parsers for expressions.10 11#include "expr-parsers.h"12#include "basic-parsers.h"13#include "misc-parsers.h"14#include "stmt-parser.h"15#include "token-parsers.h"16#include "type-parser-implementation.h"17#include "flang/Parser/characters.h"18#include "flang/Parser/parse-tree.h"19 20namespace Fortran::parser {21 22// R764 boz-literal-constant -> binary-constant | octal-constant | hex-constant23// R765 binary-constant -> B ' digit [digit]... ' | B " digit [digit]... "24// R766 octal-constant -> O ' digit [digit]... ' | O " digit [digit]... "25// R767 hex-constant ->26//        Z ' hex-digit [hex-digit]... ' | Z " hex-digit [hex-digit]... "27// extension: X accepted for Z28// extension: BOZX suffix accepted29TYPE_PARSER(construct<BOZLiteralConstant>(BOZLiteral{}))30 31// R769 array-constructor -> (/ ac-spec /) | lbracket ac-spec rbracket32TYPE_CONTEXT_PARSER("array constructor"_en_US,33    construct<ArrayConstructor>(34        "(/" >> Parser<AcSpec>{} / "/)" || bracketed(Parser<AcSpec>{})))35 36// R770 ac-spec -> type-spec :: | [type-spec ::] ac-value-list37TYPE_PARSER(construct<AcSpec>(maybe(typeSpec / "::"),38                nonemptyList("expected array constructor values"_err_en_US,39                    Parser<AcValue>{})) ||40    construct<AcSpec>(typeSpec / "::"))41 42// R773 ac-value -> expr | ac-implied-do43TYPE_PARSER(44    // PGI/Intel extension: accept triplets in array constructors45    extension<LanguageFeature::TripletInArrayConstructor>(46        "nonstandard usage: triplet in array constructor"_port_en_US,47        construct<AcValue>(construct<AcValue::Triplet>(scalarIntExpr,48            ":" >> scalarIntExpr, maybe(":" >> scalarIntExpr)))) ||49    construct<AcValue>(indirect(expr)) ||50    construct<AcValue>(indirect(Parser<AcImpliedDo>{})))51 52// R774 ac-implied-do -> ( ac-value-list , ac-implied-do-control )53TYPE_PARSER(parenthesized(54    construct<AcImpliedDo>(nonemptyList(Parser<AcValue>{} / lookAhead(","_tok)),55        "," >> Parser<AcImpliedDoControl>{})))56 57// R775 ac-implied-do-control ->58//        [integer-type-spec ::] ac-do-variable = scalar-int-expr ,59//        scalar-int-expr [, scalar-int-expr]60// R776 ac-do-variable -> do-variable61TYPE_PARSER(construct<AcImpliedDoControl>(62    maybe(integerTypeSpec / "::"), loopBounds(scalarIntExpr)))63 64// R1001 primary ->65//         literal-constant | designator | array-constructor |66//         structure-constructor | function-reference | type-param-inquiry |67//         type-param-name | ( expr )68// type-param-inquiry is parsed as a structure component, except for69// substring%KIND/LEN70constexpr auto primary{instrumented("primary"_en_US,71    first(construct<Expr>(indirect(charLiteralConstantSubstring)),72        construct<Expr>(literalConstant),73        construct<Expr>(construct<Expr::Parentheses>("(" >>74            expr / !","_tok / recovery(")"_tok, SkipPastNested<'(', ')'>{}))),75        construct<Expr>(indirect(functionReference) / !"("_tok / !"%"_tok),76        construct<Expr>(designator / !"("_tok / !"%"_tok),77        construct<Expr>(indirect(Parser<SubstringInquiry>{})), // %LEN or %KIND78        construct<Expr>(Parser<StructureConstructor>{}),79        construct<Expr>(Parser<ArrayConstructor>{}),80        // PGI/XLF extension: COMPLEX constructor (x,y)81        construct<Expr>(parenthesized(82            construct<Expr::ComplexConstructor>(expr, "," >> expr))),83        extension<LanguageFeature::PercentLOC>(84            "nonstandard usage: %LOC"_port_en_US,85            construct<Expr>("%LOC" >> parenthesized(construct<Expr::PercentLoc>(86                                          indirect(variable)))))))};87 88// R1002 level-1-expr -> [defined-unary-op] primary89// TODO: Reasonable extension: permit multiple defined-unary-ops90constexpr auto level1Expr{sourced(91    primary || // must come before define op to resolve .TRUE._8 ambiguity92    construct<Expr>(construct<Expr::DefinedUnary>(definedOpName, primary)))};93 94// R1004 mult-operand -> level-1-expr [power-op mult-operand]95// R1007 power-op -> **96// Exponentiation (**) is Fortran's only right-associative binary operation.97struct MultOperand {98  using resultType = Expr;99  constexpr MultOperand() {}100  static inline std::optional<Expr> Parse(ParseState &);101};102 103// Extension: allow + or - before a mult-operand104// Such a unary operand has lower precedence than exponentiation,105// so -x**2 is -(x**2), not (-x)**2; this matches all other106// compilers with this extension.107static constexpr auto standardMultOperand{sourced(MultOperand{})};108static constexpr auto multOperand{standardMultOperand ||109    extension<LanguageFeature::SignedMultOperand>(110        "nonstandard usage: signed mult-operand"_port_en_US,111        construct<Expr>(112            construct<Expr::UnaryPlus>("+" >> standardMultOperand))) ||113    extension<LanguageFeature::SignedMultOperand>(114        "nonstandard usage: signed mult-operand"_port_en_US,115        construct<Expr>(construct<Expr::Negate>("-" >> standardMultOperand)))};116 117inline std::optional<Expr> MultOperand::Parse(ParseState &state) {118  std::optional<Expr> result{level1Expr.Parse(state)};119  if (result) {120    static constexpr auto op{attempt("**"_tok)};121    if (op.Parse(state)) {122      std::function<Expr(Expr &&)> power{[&result](Expr &&right) {123        return Expr{Expr::Power(std::move(result).value(), std::move(right))};124      }};125      return applyLambda(power, multOperand).Parse(state); // right-recursive126    }127  }128  return result;129}130 131// R1005 add-operand -> [add-operand mult-op] mult-operand132// R1008 mult-op -> * | /133// The left recursion in the grammar is implemented iteratively.134struct AddOperand {135  using resultType = Expr;136  constexpr AddOperand() {}137  static inline std::optional<Expr> Parse(ParseState &state) {138    std::optional<Expr> result{multOperand.Parse(state)};139    if (result) {140      auto source{result->source};141      std::function<Expr(Expr &&)> multiply{[&result](Expr &&right) {142        return Expr{143            Expr::Multiply(std::move(result).value(), std::move(right))};144      }};145      std::function<Expr(Expr &&)> divide{[&result](Expr &&right) {146        return Expr{Expr::Divide(std::move(result).value(), std::move(right))};147      }};148      auto more{attempt(sourced("*" >> applyLambda(multiply, multOperand) ||149          "/" >> applyLambda(divide, multOperand)))};150      while (std::optional<Expr> next{more.Parse(state)}) {151        result = std::move(next);152        result->source.ExtendToCover(source);153      }154    }155    return result;156  }157};158constexpr AddOperand addOperand;159 160// R1006 level-2-expr -> [[level-2-expr] add-op] add-operand161// R1009 add-op -> + | -162// These are left-recursive productions, implemented iteratively.163// Note that standard Fortran admits a unary + or - to appear only here,164// by means of a missing first operand; e.g., 2*-3 is valid in C but not165// standard Fortran.  We accept unary + and - to appear before any primary166// as an extension.167struct Level2Expr {168  using resultType = Expr;169  constexpr Level2Expr() {}170  static inline std::optional<Expr> Parse(ParseState &state) {171    static constexpr auto unary{172        sourced(173            construct<Expr>(construct<Expr::UnaryPlus>("+" >> addOperand)) ||174            construct<Expr>(construct<Expr::Negate>("-" >> addOperand))) ||175        addOperand};176    std::optional<Expr> result{unary.Parse(state)};177    if (result) {178      auto source{result->source};179      std::function<Expr(Expr &&)> add{[&result](Expr &&right) {180        return Expr{Expr::Add(std::move(result).value(), std::move(right))};181      }};182      std::function<Expr(Expr &&)> subtract{[&result](Expr &&right) {183        return Expr{184            Expr::Subtract(std::move(result).value(), std::move(right))};185      }};186      auto more{attempt(sourced("+" >> applyLambda(add, addOperand) ||187          "-" >> applyLambda(subtract, addOperand)))};188      while (std::optional<Expr> next{more.Parse(state)}) {189        result = std::move(next);190        result->source.ExtendToCover(source);191      }192    }193    return result;194  }195};196constexpr Level2Expr level2Expr;197 198// R1010 level-3-expr -> [level-3-expr concat-op] level-2-expr199// R1011 concat-op -> //200// Concatenation (//) is left-associative for parsing performance, although201// one would never notice if it were right-associated.202struct Level3Expr {203  using resultType = Expr;204  constexpr Level3Expr() {}205  static inline std::optional<Expr> Parse(ParseState &state) {206    std::optional<Expr> result{level2Expr.Parse(state)};207    if (result) {208      auto source{result->source};209      std::function<Expr(Expr &&)> concat{[&result](Expr &&right) {210        return Expr{Expr::Concat(std::move(result).value(), std::move(right))};211      }};212      auto more{attempt(sourced("//" >> applyLambda(concat, level2Expr)))};213      while (std::optional<Expr> next{more.Parse(state)}) {214        result = std::move(next);215        result->source.ExtendToCover(source);216      }217    }218    return result;219  }220};221constexpr Level3Expr level3Expr;222 223// R1012 level-4-expr -> [level-3-expr rel-op] level-3-expr224// R1013 rel-op ->225//         .EQ. | .NE. | .LT. | .LE. | .GT. | .GE. |226//          == | /= | < | <= | > | >=  @ | <>227// N.B. relations are not recursive (i.e., LOGICAL is not ordered)228struct Level4Expr {229  using resultType = Expr;230  constexpr Level4Expr() {}231  static inline std::optional<Expr> Parse(ParseState &state) {232    std::optional<Expr> result{level3Expr.Parse(state)};233    if (result) {234      auto source{result->source};235      std::function<Expr(Expr &&)> lt{[&result](Expr &&right) {236        return Expr{Expr::LT(std::move(result).value(), std::move(right))};237      }};238      std::function<Expr(Expr &&)> le{[&result](Expr &&right) {239        return Expr{Expr::LE(std::move(result).value(), std::move(right))};240      }};241      std::function<Expr(Expr &&)> eq{[&result](Expr &&right) {242        return Expr{Expr::EQ(std::move(result).value(), std::move(right))};243      }};244      std::function<Expr(Expr &&)> ne{[&result](Expr &&right) {245        return Expr{Expr::NE(std::move(result).value(), std::move(right))};246      }};247      std::function<Expr(Expr &&)> ge{[&result](Expr &&right) {248        return Expr{Expr::GE(std::move(result).value(), std::move(right))};249      }};250      std::function<Expr(Expr &&)> gt{[&result](Expr &&right) {251        return Expr{Expr::GT(std::move(result).value(), std::move(right))};252      }};253      auto more{attempt(254          sourced((".LT."_tok || "<"_tok) >> applyLambda(lt, level3Expr) ||255              (".LE."_tok || "<="_tok) >> applyLambda(le, level3Expr) ||256              (".EQ."_tok || "=="_tok) >> applyLambda(eq, level3Expr) ||257              (".NE."_tok || "/="_tok ||258                  extension<LanguageFeature::AlternativeNE>(259                      "nonstandard usage: <> for /= or .NE."_port_en_US,260                      "<>"_tok /* PGI/Cray extension; Cray also has .LG. */)) >>261                  applyLambda(ne, level3Expr) ||262              (".GE."_tok || ">="_tok) >> applyLambda(ge, level3Expr) ||263              (".GT."_tok || ">"_tok) >> applyLambda(gt, level3Expr)))};264      if (std::optional<Expr> next{more.Parse(state)}) {265        next->source.ExtendToCover(source);266        return next;267      }268    }269    return result;270  }271};272constexpr Level4Expr level4Expr;273 274// R1014 and-operand -> [not-op] level-4-expr275// R1018 not-op -> .NOT.276// N.B. Fortran's .NOT. binds less tightly than its comparison operators do.277// PGI/Intel extension: accept multiple .NOT. operators278struct AndOperand {279  using resultType = Expr;280  constexpr AndOperand() {}281  static inline std::optional<Expr> Parse(ParseState &);282};283constexpr AndOperand andOperand;284 285// Match a logical operator or, optionally, its abbreviation.286inline constexpr auto logicalOp(const char *op, const char *abbrev) {287  return TokenStringMatch{op} ||288      extension<LanguageFeature::LogicalAbbreviations>(289          "nonstandard usage: abbreviated LOGICAL operator"_port_en_US,290          TokenStringMatch{abbrev});291}292 293inline std::optional<Expr> AndOperand::Parse(ParseState &state) {294  static constexpr auto notOp{attempt(logicalOp(".NOT.", ".N.") >> andOperand)};295  if (std::optional<Expr> negation{notOp.Parse(state)}) {296    return Expr{Expr::NOT{std::move(*negation)}};297  } else {298    return level4Expr.Parse(state);299  }300}301 302// R1015 or-operand -> [or-operand and-op] and-operand303// R1019 and-op -> .AND.304// .AND. is left-associative305struct OrOperand {306  using resultType = Expr;307  constexpr OrOperand() {}308  static inline std::optional<Expr> Parse(ParseState &state) {309    static constexpr auto operand{sourced(andOperand)};310    std::optional<Expr> result{operand.Parse(state)};311    if (result) {312      auto source{result->source};313      std::function<Expr(Expr &&)> logicalAnd{[&result](Expr &&right) {314        return Expr{Expr::AND(std::move(result).value(), std::move(right))};315      }};316      auto more{attempt(sourced(317          logicalOp(".AND.", ".A.") >> applyLambda(logicalAnd, andOperand)))};318      while (std::optional<Expr> next{more.Parse(state)}) {319        result = std::move(next);320        result->source.ExtendToCover(source);321      }322    }323    return result;324  }325};326constexpr OrOperand orOperand;327 328// R1016 equiv-operand -> [equiv-operand or-op] or-operand329// R1020 or-op -> .OR.330// .OR. is left-associative331struct EquivOperand {332  using resultType = Expr;333  constexpr EquivOperand() {}334  static inline std::optional<Expr> Parse(ParseState &state) {335    std::optional<Expr> result{orOperand.Parse(state)};336    if (result) {337      auto source{result->source};338      std::function<Expr(Expr &&)> logicalOr{[&result](Expr &&right) {339        return Expr{Expr::OR(std::move(result).value(), std::move(right))};340      }};341      auto more{attempt(sourced(342          logicalOp(".OR.", ".O.") >> applyLambda(logicalOr, orOperand)))};343      while (std::optional<Expr> next{more.Parse(state)}) {344        result = std::move(next);345        result->source.ExtendToCover(source);346      }347    }348    return result;349  }350};351constexpr EquivOperand equivOperand;352 353// R1017 level-5-expr -> [level-5-expr equiv-op] equiv-operand354// R1021 equiv-op -> .EQV. | .NEQV.355// Logical equivalence is left-associative.356// Extension: .XOR. as synonym for .NEQV.357struct Level5Expr {358  using resultType = Expr;359  constexpr Level5Expr() {}360  static inline std::optional<Expr> Parse(ParseState &state) {361    std::optional<Expr> result{equivOperand.Parse(state)};362    if (result) {363      auto source{result->source};364      std::function<Expr(Expr &&)> eqv{[&result](Expr &&right) {365        return Expr{Expr::EQV(std::move(result).value(), std::move(right))};366      }};367      std::function<Expr(Expr &&)> neqv{[&result](Expr &&right) {368        return Expr{Expr::NEQV(std::move(result).value(), std::move(right))};369      }};370      auto more{attempt(sourced(".EQV." >> applyLambda(eqv, equivOperand) ||371          (".NEQV."_tok ||372              extension<LanguageFeature::XOROperator>(373                  "nonstandard usage: .XOR./.X. spelling of .NEQV."_port_en_US,374                  logicalOp(".XOR.", ".X."))) >>375              applyLambda(neqv, equivOperand)))};376      while (std::optional<Expr> next{more.Parse(state)}) {377        result = std::move(next);378        result->source.ExtendToCover(source);379      }380    }381    return result;382  }383};384constexpr Level5Expr level5Expr;385 386// R1022 expr -> [expr defined-binary-op] level-5-expr387// Defined binary operators associate leftwards.388template <> std::optional<Expr> Parser<Expr>::Parse(ParseState &state) {389  std::optional<Expr> result{level5Expr.Parse(state)};390  if (result) {391    auto source{result->source};392    std::function<Expr(DefinedOpName &&, Expr &&)> defBinOp{393        [&result](DefinedOpName &&op, Expr &&right) {394          return Expr{Expr::DefinedBinary(395              std::move(op), std::move(result).value(), std::move(right))};396        }};397    auto more{attempt(398        sourced(applyLambda<Expr>(defBinOp, definedOpName, level5Expr)))};399    while (std::optional<Expr> next{more.Parse(state)}) {400      result = std::move(next);401      result->source.ExtendToCover(source);402    }403  }404  return result;405}406 407// R1003 defined-unary-op -> . letter [letter]... .408// R1023 defined-binary-op -> . letter [letter]... .409// R1414 local-defined-operator -> defined-unary-op | defined-binary-op410// R1415 use-defined-operator -> defined-unary-op | defined-binary-op411// C1003 A defined operator must be distinct from logical literal constants412// and intrinsic operator names; this is handled by attempting their parses413// first, and by name resolution on their definitions, for best errors.414// N.B. The name of the operator is captured with the dots around it.415constexpr auto definedOpNameChar{letter ||416    extension<LanguageFeature::PunctuationInNames>(417        "nonstandard usage: non-alphabetic character in defined operator"_port_en_US,418        "$@"_ch)};419TYPE_PARSER(420    space >> construct<DefinedOpName>(sourced("."_ch >>421                 some(definedOpNameChar) >> construct<Name>() / "."_ch)))422 423// R1028 specification-expr -> scalar-int-expr424TYPE_PARSER(construct<SpecificationExpr>(scalarIntExpr))425 426// R1032 assignment-stmt -> variable = expr427TYPE_CONTEXT_PARSER("assignment statement"_en_US,428    construct<AssignmentStmt>(variable / "=", expr))429 430// R1033 pointer-assignment-stmt ->431//         data-pointer-object [( bounds-spec-list )] => data-target |432//         data-pointer-object ( bounds-remapping-list ) => data-target |433//         proc-pointer-object => proc-target434// R1034 data-pointer-object ->435//         variable-name | scalar-variable % data-pointer-component-name436//   C1022 a scalar-variable shall be a data-ref437//   C1024 a data-pointer-object shall not be a coindexed object438// R1038 proc-pointer-object -> proc-pointer-name | proc-component-ref439//440// A distinction can't be made at the time of the initial parse between441// data-pointer-object and proc-pointer-object, or between data-target442// and proc-target.443TYPE_CONTEXT_PARSER("pointer assignment statement"_en_US,444    construct<PointerAssignmentStmt>(dataRef,445        parenthesized(nonemptyList(Parser<BoundsRemapping>{})), "=>" >> expr) ||446        construct<PointerAssignmentStmt>(dataRef,447            defaulted(parenthesized(nonemptyList(Parser<BoundsSpec>{}))),448            "=>" >> expr))449 450// R1035 bounds-spec -> lower-bound-expr :451TYPE_PARSER(construct<BoundsSpec>(boundExpr / ":"))452 453// R1036 bounds-remapping -> lower-bound-expr : upper-bound-expr454TYPE_PARSER(construct<BoundsRemapping>(boundExpr / ":", boundExpr))455 456// R1039 proc-component-ref -> scalar-variable % procedure-component-name457//   C1027 the scalar-variable must be a data-ref without coindices.458TYPE_PARSER(construct<ProcComponentRef>(structureComponent))459 460// R1041 where-stmt -> WHERE ( mask-expr ) where-assignment-stmt461// R1045 where-assignment-stmt -> assignment-stmt462// R1046 mask-expr -> logical-expr463TYPE_CONTEXT_PARSER("WHERE statement"_en_US,464    construct<WhereStmt>("WHERE" >> parenthesized(logicalExpr), assignmentStmt))465 466// R1042 where-construct ->467//         where-construct-stmt [where-body-construct]...468//         [masked-elsewhere-stmt [where-body-construct]...]...469//         [elsewhere-stmt [where-body-construct]...] end-where-stmt470TYPE_CONTEXT_PARSER("WHERE construct"_en_US,471    construct<WhereConstruct>(statement(Parser<WhereConstructStmt>{}),472        many(whereBodyConstruct),473        many(construct<WhereConstruct::MaskedElsewhere>(474            statement(Parser<MaskedElsewhereStmt>{}),475            many(whereBodyConstruct))),476        maybe(construct<WhereConstruct::Elsewhere>(477            statement(Parser<ElsewhereStmt>{}), many(whereBodyConstruct))),478        statement(Parser<EndWhereStmt>{})))479 480// R1043 where-construct-stmt -> [where-construct-name :] WHERE ( mask-expr )481TYPE_CONTEXT_PARSER("WHERE construct statement"_en_US,482    construct<WhereConstructStmt>(483        maybe(name / ":"), "WHERE" >> parenthesized(logicalExpr)))484 485// R1044 where-body-construct ->486//         where-assignment-stmt | where-stmt | where-construct487TYPE_PARSER(construct<WhereBodyConstruct>(statement(assignmentStmt)) ||488    construct<WhereBodyConstruct>(statement(whereStmt)) ||489    construct<WhereBodyConstruct>(indirect(whereConstruct)))490 491// R1047 masked-elsewhere-stmt ->492//         ELSEWHERE ( mask-expr ) [where-construct-name]493TYPE_CONTEXT_PARSER("masked ELSEWHERE statement"_en_US,494    construct<MaskedElsewhereStmt>(495        "ELSE WHERE" >> parenthesized(logicalExpr), maybe(name)))496 497// R1048 elsewhere-stmt -> ELSEWHERE [where-construct-name]498TYPE_CONTEXT_PARSER("ELSEWHERE statement"_en_US,499    construct<ElsewhereStmt>("ELSE WHERE" >> maybe(name)))500 501// R1049 end-where-stmt -> ENDWHERE [where-construct-name]502TYPE_CONTEXT_PARSER("END WHERE statement"_en_US,503    construct<EndWhereStmt>(recovery(504        "END WHERE" >> maybe(name), namedConstructEndStmtErrorRecovery)))505 506// R1050 forall-construct ->507//         forall-construct-stmt [forall-body-construct]... end-forall-stmt508TYPE_CONTEXT_PARSER("FORALL construct"_en_US,509    construct<ForallConstruct>(statement(Parser<ForallConstructStmt>{}),510        many(Parser<ForallBodyConstruct>{}),511        statement(Parser<EndForallStmt>{})))512 513// R1051 forall-construct-stmt ->514//         [forall-construct-name :] FORALL concurrent-header515TYPE_CONTEXT_PARSER("FORALL construct statement"_en_US,516    construct<ForallConstructStmt>(517        maybe(name / ":"), "FORALL" >> indirect(concurrentHeader)))518 519// R1052 forall-body-construct ->520//         forall-assignment-stmt | where-stmt | where-construct |521//         forall-construct | forall-stmt522TYPE_PARSER(construct<ForallBodyConstruct>(statement(forallAssignmentStmt)) ||523    construct<ForallBodyConstruct>(statement(whereStmt)) ||524    construct<ForallBodyConstruct>(whereConstruct) ||525    construct<ForallBodyConstruct>(indirect(forallConstruct)) ||526    construct<ForallBodyConstruct>(statement(forallStmt)))527 528// R1053 forall-assignment-stmt -> assignment-stmt | pointer-assignment-stmt529TYPE_PARSER(construct<ForallAssignmentStmt>(assignmentStmt) ||530    construct<ForallAssignmentStmt>(pointerAssignmentStmt))531 532// R1054 end-forall-stmt -> END FORALL [forall-construct-name]533TYPE_CONTEXT_PARSER("END FORALL statement"_en_US,534    construct<EndForallStmt>(recovery(535        "END FORALL" >> maybe(name), namedConstructEndStmtErrorRecovery)))536 537// R1055 forall-stmt -> FORALL concurrent-header forall-assignment-stmt538TYPE_CONTEXT_PARSER("FORALL statement"_en_US,539    construct<ForallStmt>("FORALL" >> indirect(concurrentHeader),540        unlabeledStatement(forallAssignmentStmt)))541} // namespace Fortran::parser542