xref: /llvm-project/flang/lib/Parser/expr-parsers.cpp (revision 1f8790050b0e99e7b46cc69518aa84f46f50738e)
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-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 // Per-type parsers for expressions.
10 
11 #include "expr-parsers.h"
12 #include "basic-parsers.h"
13 #include "debug-parser.h"
14 #include "misc-parsers.h"
15 #include "stmt-parser.h"
16 #include "token-parsers.h"
17 #include "type-parser-implementation.h"
18 #include "flang/Parser/characters.h"
19 #include "flang/Parser/parse-tree.h"
20 
21 namespace Fortran::parser {
22 
23 // R764 boz-literal-constant -> binary-constant | octal-constant | hex-constant
24 // R765 binary-constant -> B ' digit [digit]... ' | B " digit [digit]... "
25 // R766 octal-constant -> O ' digit [digit]... ' | O " digit [digit]... "
26 // R767 hex-constant ->
27 //        Z ' hex-digit [hex-digit]... ' | Z " hex-digit [hex-digit]... "
28 // extension: X accepted for Z
29 // extension: BOZX suffix accepted
30 TYPE_PARSER(construct<BOZLiteralConstant>(BOZLiteral{}))
31 
32 // R769 array-constructor -> (/ ac-spec /) | lbracket ac-spec rbracket
33 TYPE_CONTEXT_PARSER("array constructor"_en_US,
34     construct<ArrayConstructor>(
35         "(/" >> Parser<AcSpec>{} / "/)" || bracketed(Parser<AcSpec>{})))
36 
37 // R770 ac-spec -> type-spec :: | [type-spec ::] ac-value-list
38 TYPE_PARSER(construct<AcSpec>(maybe(typeSpec / "::"),
39                 nonemptyList("expected array constructor values"_err_en_US,
40                     Parser<AcValue>{})) ||
41     construct<AcSpec>(typeSpec / "::"))
42 
43 // R773 ac-value -> expr | ac-implied-do
44 TYPE_PARSER(
45     // PGI/Intel extension: accept triplets in array constructors
46     extension<LanguageFeature::TripletInArrayConstructor>(
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 )
53 TYPE_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-variable
61 TYPE_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 // N.B. type-param-inquiry is parsed as a structure component
69 constexpr auto primary{instrumented("primary"_en_US,
70     first(construct<Expr>(indirect(Parser<CharLiteralConstantSubstring>{})),
71         construct<Expr>(literalConstant),
72         construct<Expr>(construct<Expr::Parentheses>(parenthesized(expr))),
73         construct<Expr>(indirect(functionReference) / !"("_tok),
74         construct<Expr>(designator / !"("_tok),
75         construct<Expr>(Parser<StructureConstructor>{}),
76         construct<Expr>(Parser<ArrayConstructor>{}),
77         // PGI/XLF extension: COMPLEX constructor (x,y)
78         extension<LanguageFeature::ComplexConstructor>(
79             construct<Expr>(parenthesized(
80                 construct<Expr::ComplexConstructor>(expr, "," >> expr)))),
81         extension<LanguageFeature::PercentLOC>(construct<Expr>("%LOC" >>
82             parenthesized(construct<Expr::PercentLoc>(indirect(variable)))))))};
83 
84 // R1002 level-1-expr -> [defined-unary-op] primary
85 // TODO: Reasonable extension: permit multiple defined-unary-ops
86 constexpr auto level1Expr{sourced(
87     first(primary, // must come before define op to resolve .TRUE._8 ambiguity
88         construct<Expr>(construct<Expr::DefinedUnary>(definedOpName, primary)),
89         extension<LanguageFeature::SignedPrimary>(
90             construct<Expr>(construct<Expr::UnaryPlus>("+" >> primary))),
91         extension<LanguageFeature::SignedPrimary>(
92             construct<Expr>(construct<Expr::Negate>("-" >> 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.
97 struct MultOperand {
98   using resultType = Expr;
99   constexpr MultOperand() {}
100   static inline std::optional<Expr> Parse(ParseState &);
101 };
102 
103 static constexpr auto multOperand{sourced(MultOperand{})};
104 
105 inline std::optional<Expr> MultOperand::Parse(ParseState &state) {
106   std::optional<Expr> result{level1Expr.Parse(state)};
107   if (result) {
108     static constexpr auto op{attempt("**"_tok)};
109     if (op.Parse(state)) {
110       std::function<Expr(Expr &&)> power{[&result](Expr &&right) {
111         return Expr{Expr::Power(std::move(result).value(), std::move(right))};
112       }};
113       return applyLambda(power, multOperand).Parse(state); // right-recursive
114     }
115   }
116   return result;
117 }
118 
119 // R1005 add-operand -> [add-operand mult-op] mult-operand
120 // R1008 mult-op -> * | /
121 // The left recursion in the grammar is implemented iteratively.
122 constexpr struct AddOperand {
123   using resultType = Expr;
124   constexpr AddOperand() {}
125   static inline std::optional<Expr> Parse(ParseState &state) {
126     std::optional<Expr> result{multOperand.Parse(state)};
127     if (result) {
128       auto source{result->source};
129       std::function<Expr(Expr &&)> multiply{[&result](Expr &&right) {
130         return Expr{
131             Expr::Multiply(std::move(result).value(), std::move(right))};
132       }};
133       std::function<Expr(Expr &&)> divide{[&result](Expr &&right) {
134         return Expr{Expr::Divide(std::move(result).value(), std::move(right))};
135       }};
136       auto more{attempt(sourced("*" >> applyLambda(multiply, multOperand) ||
137           "/" >> applyLambda(divide, multOperand)))};
138       while (std::optional<Expr> next{more.Parse(state)}) {
139         result = std::move(next);
140         result->source.ExtendToCover(source);
141       }
142     }
143     return result;
144   }
145 } addOperand;
146 
147 // R1006 level-2-expr -> [[level-2-expr] add-op] add-operand
148 // R1009 add-op -> + | -
149 // These are left-recursive productions, implemented iteratively.
150 // Note that standard Fortran admits a unary + or - to appear only here,
151 // by means of a missing first operand; e.g., 2*-3 is valid in C but not
152 // standard Fortran.  We accept unary + and - to appear before any primary
153 // as an extension.
154 constexpr struct Level2Expr {
155   using resultType = Expr;
156   constexpr Level2Expr() {}
157   static inline std::optional<Expr> Parse(ParseState &state) {
158     static constexpr auto unary{
159         sourced(
160             construct<Expr>(construct<Expr::UnaryPlus>("+" >> addOperand)) ||
161             construct<Expr>(construct<Expr::Negate>("-" >> addOperand))) ||
162         addOperand};
163     std::optional<Expr> result{unary.Parse(state)};
164     if (result) {
165       auto source{result->source};
166       std::function<Expr(Expr &&)> add{[&result](Expr &&right) {
167         return Expr{Expr::Add(std::move(result).value(), std::move(right))};
168       }};
169       std::function<Expr(Expr &&)> subtract{[&result](Expr &&right) {
170         return Expr{
171             Expr::Subtract(std::move(result).value(), std::move(right))};
172       }};
173       auto more{attempt(sourced("+" >> applyLambda(add, addOperand) ||
174           "-" >> applyLambda(subtract, addOperand)))};
175       while (std::optional<Expr> next{more.Parse(state)}) {
176         result = std::move(next);
177         result->source.ExtendToCover(source);
178       }
179     }
180     return result;
181   }
182 } level2Expr;
183 
184 // R1010 level-3-expr -> [level-3-expr concat-op] level-2-expr
185 // R1011 concat-op -> //
186 // Concatenation (//) is left-associative for parsing performance, although
187 // one would never notice if it were right-associated.
188 constexpr struct Level3Expr {
189   using resultType = Expr;
190   constexpr Level3Expr() {}
191   static inline std::optional<Expr> Parse(ParseState &state) {
192     std::optional<Expr> result{level2Expr.Parse(state)};
193     if (result) {
194       auto source{result->source};
195       std::function<Expr(Expr &&)> concat{[&result](Expr &&right) {
196         return Expr{Expr::Concat(std::move(result).value(), std::move(right))};
197       }};
198       auto more{attempt(sourced("//" >> applyLambda(concat, level2Expr)))};
199       while (std::optional<Expr> next{more.Parse(state)}) {
200         result = std::move(next);
201         result->source.ExtendToCover(source);
202       }
203     }
204     return result;
205   }
206 } level3Expr;
207 
208 // R1012 level-4-expr -> [level-3-expr rel-op] level-3-expr
209 // R1013 rel-op ->
210 //         .EQ. | .NE. | .LT. | .LE. | .GT. | .GE. |
211 //          == | /= | < | <= | > | >=  @ | <>
212 // N.B. relations are not recursive (i.e., LOGICAL is not ordered)
213 constexpr struct Level4Expr {
214   using resultType = Expr;
215   constexpr Level4Expr() {}
216   static inline std::optional<Expr> Parse(ParseState &state) {
217     std::optional<Expr> result{level3Expr.Parse(state)};
218     if (result) {
219       auto source{result->source};
220       std::function<Expr(Expr &&)> lt{[&result](Expr &&right) {
221         return Expr{Expr::LT(std::move(result).value(), std::move(right))};
222       }};
223       std::function<Expr(Expr &&)> le{[&result](Expr &&right) {
224         return Expr{Expr::LE(std::move(result).value(), std::move(right))};
225       }};
226       std::function<Expr(Expr &&)> eq{[&result](Expr &&right) {
227         return Expr{Expr::EQ(std::move(result).value(), std::move(right))};
228       }};
229       std::function<Expr(Expr &&)> ne{[&result](Expr &&right) {
230         return Expr{Expr::NE(std::move(result).value(), std::move(right))};
231       }};
232       std::function<Expr(Expr &&)> ge{[&result](Expr &&right) {
233         return Expr{Expr::GE(std::move(result).value(), std::move(right))};
234       }};
235       std::function<Expr(Expr &&)> gt{[&result](Expr &&right) {
236         return Expr{Expr::GT(std::move(result).value(), std::move(right))};
237       }};
238       auto more{attempt(
239           sourced((".LT."_tok || "<"_tok) >> applyLambda(lt, level3Expr) ||
240               (".LE."_tok || "<="_tok) >> applyLambda(le, level3Expr) ||
241               (".EQ."_tok || "=="_tok) >> applyLambda(eq, level3Expr) ||
242               (".NE."_tok || "/="_tok ||
243                   extension<LanguageFeature::AlternativeNE>(
244                       "<>"_tok /* PGI/Cray extension; Cray also has .LG. */)) >>
245                   applyLambda(ne, level3Expr) ||
246               (".GE."_tok || ">="_tok) >> applyLambda(ge, level3Expr) ||
247               (".GT."_tok || ">"_tok) >> applyLambda(gt, level3Expr)))};
248       if (std::optional<Expr> next{more.Parse(state)}) {
249         next->source.ExtendToCover(source);
250         return next;
251       }
252     }
253     return result;
254   }
255 } level4Expr;
256 
257 // R1014 and-operand -> [not-op] level-4-expr
258 // R1018 not-op -> .NOT.
259 // N.B. Fortran's .NOT. binds less tightly than its comparison operators do.
260 // PGI/Intel extension: accept multiple .NOT. operators
261 constexpr struct AndOperand {
262   using resultType = Expr;
263   constexpr AndOperand() {}
264   static inline std::optional<Expr> Parse(ParseState &);
265 } andOperand;
266 
267 // Match a logical operator or, optionally, its abbreviation.
268 inline constexpr auto logicalOp(const char *op, const char *abbrev) {
269   return TokenStringMatch{op} ||
270       extension<LanguageFeature::LogicalAbbreviations>(
271           TokenStringMatch{abbrev});
272 }
273 
274 inline std::optional<Expr> AndOperand::Parse(ParseState &state) {
275   static constexpr auto notOp{attempt(logicalOp(".NOT.", ".N.") >> andOperand)};
276   if (std::optional<Expr> negation{notOp.Parse(state)}) {
277     return Expr{Expr::NOT{std::move(*negation)}};
278   } else {
279     return level4Expr.Parse(state);
280   }
281 }
282 
283 // R1015 or-operand -> [or-operand and-op] and-operand
284 // R1019 and-op -> .AND.
285 // .AND. is left-associative
286 constexpr struct OrOperand {
287   using resultType = Expr;
288   constexpr OrOperand() {}
289   static inline std::optional<Expr> Parse(ParseState &state) {
290     static constexpr auto operand{sourced(andOperand)};
291     std::optional<Expr> result{operand.Parse(state)};
292     if (result) {
293       auto source{result->source};
294       std::function<Expr(Expr &&)> logicalAnd{[&result](Expr &&right) {
295         return Expr{Expr::AND(std::move(result).value(), std::move(right))};
296       }};
297       auto more{attempt(sourced(
298           logicalOp(".AND.", ".A.") >> applyLambda(logicalAnd, andOperand)))};
299       while (std::optional<Expr> next{more.Parse(state)}) {
300         result = std::move(next);
301         result->source.ExtendToCover(source);
302       }
303     }
304     return result;
305   }
306 } orOperand;
307 
308 // R1016 equiv-operand -> [equiv-operand or-op] or-operand
309 // R1020 or-op -> .OR.
310 // .OR. is left-associative
311 constexpr struct EquivOperand {
312   using resultType = Expr;
313   constexpr EquivOperand() {}
314   static inline std::optional<Expr> Parse(ParseState &state) {
315     std::optional<Expr> result{orOperand.Parse(state)};
316     if (result) {
317       auto source{result->source};
318       std::function<Expr(Expr &&)> logicalOr{[&result](Expr &&right) {
319         return Expr{Expr::OR(std::move(result).value(), std::move(right))};
320       }};
321       auto more{attempt(sourced(
322           logicalOp(".OR.", ".O.") >> applyLambda(logicalOr, orOperand)))};
323       while (std::optional<Expr> next{more.Parse(state)}) {
324         result = std::move(next);
325         result->source.ExtendToCover(source);
326       }
327     }
328     return result;
329   }
330 } equivOperand;
331 
332 // R1017 level-5-expr -> [level-5-expr equiv-op] equiv-operand
333 // R1021 equiv-op -> .EQV. | .NEQV.
334 // Logical equivalence is left-associative.
335 // Extension: .XOR. as synonym for .NEQV.
336 constexpr struct Level5Expr {
337   using resultType = Expr;
338   constexpr Level5Expr() {}
339   static inline std::optional<Expr> Parse(ParseState &state) {
340     std::optional<Expr> result{equivOperand.Parse(state)};
341     if (result) {
342       auto source{result->source};
343       std::function<Expr(Expr &&)> eqv{[&result](Expr &&right) {
344         return Expr{Expr::EQV(std::move(result).value(), std::move(right))};
345       }};
346       std::function<Expr(Expr &&)> neqv{[&result](Expr &&right) {
347         return Expr{Expr::NEQV(std::move(result).value(), std::move(right))};
348       }};
349       auto more{attempt(sourced(".EQV." >> applyLambda(eqv, equivOperand) ||
350           (".NEQV."_tok ||
351               extension<LanguageFeature::XOROperator>(
352                   logicalOp(".XOR.", ".X."))) >>
353               applyLambda(neqv, equivOperand)))};
354       while (std::optional<Expr> next{more.Parse(state)}) {
355         result = std::move(next);
356         result->source.ExtendToCover(source);
357       }
358     }
359     return result;
360   }
361 } level5Expr;
362 
363 // R1022 expr -> [expr defined-binary-op] level-5-expr
364 // Defined binary operators associate leftwards.
365 template <> std::optional<Expr> Parser<Expr>::Parse(ParseState &state) {
366   std::optional<Expr> result{level5Expr.Parse(state)};
367   if (result) {
368     auto source{result->source};
369     std::function<Expr(DefinedOpName &&, Expr &&)> defBinOp{
370         [&result](DefinedOpName &&op, Expr &&right) {
371           return Expr{Expr::DefinedBinary(
372               std::move(op), std::move(result).value(), std::move(right))};
373         }};
374     auto more{
375         attempt(sourced(applyLambda(defBinOp, definedOpName, level5Expr)))};
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 
384 // R1003 defined-unary-op -> . letter [letter]... .
385 // R1023 defined-binary-op -> . letter [letter]... .
386 // R1414 local-defined-operator -> defined-unary-op | defined-binary-op
387 // R1415 use-defined-operator -> defined-unary-op | defined-binary-op
388 // C1003 A defined operator must be distinct from logical literal constants
389 // and intrinsic operator names; this is handled by attempting their parses
390 // first, and by name resolution on their definitions, for best errors.
391 // N.B. The name of the operator is captured with the dots around it.
392 constexpr auto definedOpNameChar{
393     letter || extension<LanguageFeature::PunctuationInNames>("$@"_ch)};
394 TYPE_PARSER(
395     space >> construct<DefinedOpName>(sourced("."_ch >>
396                  some(definedOpNameChar) >> construct<Name>() / "."_ch)))
397 
398 // R1028 specification-expr -> scalar-int-expr
399 TYPE_PARSER(construct<SpecificationExpr>(scalarIntExpr))
400 
401 // R1032 assignment-stmt -> variable = expr
402 TYPE_CONTEXT_PARSER("assignment statement"_en_US,
403     construct<AssignmentStmt>(variable / "=", expr))
404 
405 // R1033 pointer-assignment-stmt ->
406 //         data-pointer-object [( bounds-spec-list )] => data-target |
407 //         data-pointer-object ( bounds-remapping-list ) => data-target |
408 //         proc-pointer-object => proc-target
409 // R1034 data-pointer-object ->
410 //         variable-name | scalar-variable % data-pointer-component-name
411 //   C1022 a scalar-variable shall be a data-ref
412 //   C1024 a data-pointer-object shall not be a coindexed object
413 // R1038 proc-pointer-object -> proc-pointer-name | proc-component-ref
414 //
415 // A distinction can't be made at the time of the initial parse between
416 // data-pointer-object and proc-pointer-object, or between data-target
417 // and proc-target.
418 TYPE_CONTEXT_PARSER("pointer assignment statement"_en_US,
419     construct<PointerAssignmentStmt>(dataRef,
420         parenthesized(nonemptyList(Parser<BoundsRemapping>{})), "=>" >> expr) ||
421         construct<PointerAssignmentStmt>(dataRef,
422             defaulted(parenthesized(nonemptyList(Parser<BoundsSpec>{}))),
423             "=>" >> expr))
424 
425 // R1035 bounds-spec -> lower-bound-expr :
426 TYPE_PARSER(construct<BoundsSpec>(boundExpr / ":"))
427 
428 // R1036 bounds-remapping -> lower-bound-expr : upper-bound-expr
429 TYPE_PARSER(construct<BoundsRemapping>(boundExpr / ":", boundExpr))
430 
431 // R1039 proc-component-ref -> scalar-variable % procedure-component-name
432 //   C1027 the scalar-variable must be a data-ref without coindices.
433 TYPE_PARSER(construct<ProcComponentRef>(structureComponent))
434 
435 // R1041 where-stmt -> WHERE ( mask-expr ) where-assignment-stmt
436 // R1045 where-assignment-stmt -> assignment-stmt
437 // R1046 mask-expr -> logical-expr
438 TYPE_CONTEXT_PARSER("WHERE statement"_en_US,
439     construct<WhereStmt>("WHERE" >> parenthesized(logicalExpr), assignmentStmt))
440 
441 // R1042 where-construct ->
442 //         where-construct-stmt [where-body-construct]...
443 //         [masked-elsewhere-stmt [where-body-construct]...]...
444 //         [elsewhere-stmt [where-body-construct]...] end-where-stmt
445 TYPE_CONTEXT_PARSER("WHERE construct"_en_US,
446     construct<WhereConstruct>(statement(Parser<WhereConstructStmt>{}),
447         many(whereBodyConstruct),
448         many(construct<WhereConstruct::MaskedElsewhere>(
449             statement(Parser<MaskedElsewhereStmt>{}),
450             many(whereBodyConstruct))),
451         maybe(construct<WhereConstruct::Elsewhere>(
452             statement(Parser<ElsewhereStmt>{}), many(whereBodyConstruct))),
453         statement(Parser<EndWhereStmt>{})))
454 
455 // R1043 where-construct-stmt -> [where-construct-name :] WHERE ( mask-expr )
456 TYPE_CONTEXT_PARSER("WHERE construct statement"_en_US,
457     construct<WhereConstructStmt>(
458         maybe(name / ":"), "WHERE" >> parenthesized(logicalExpr)))
459 
460 // R1044 where-body-construct ->
461 //         where-assignment-stmt | where-stmt | where-construct
462 TYPE_PARSER(construct<WhereBodyConstruct>(statement(assignmentStmt)) ||
463     construct<WhereBodyConstruct>(statement(whereStmt)) ||
464     construct<WhereBodyConstruct>(indirect(whereConstruct)))
465 
466 // R1047 masked-elsewhere-stmt ->
467 //         ELSEWHERE ( mask-expr ) [where-construct-name]
468 TYPE_CONTEXT_PARSER("masked ELSEWHERE statement"_en_US,
469     construct<MaskedElsewhereStmt>(
470         "ELSE WHERE" >> parenthesized(logicalExpr), maybe(name)))
471 
472 // R1048 elsewhere-stmt -> ELSEWHERE [where-construct-name]
473 TYPE_CONTEXT_PARSER("ELSEWHERE statement"_en_US,
474     construct<ElsewhereStmt>("ELSE WHERE" >> maybe(name)))
475 
476 // R1049 end-where-stmt -> ENDWHERE [where-construct-name]
477 TYPE_CONTEXT_PARSER("END WHERE statement"_en_US,
478     construct<EndWhereStmt>(
479         recovery("END WHERE" >> maybe(name), endStmtErrorRecovery)))
480 
481 // R1050 forall-construct ->
482 //         forall-construct-stmt [forall-body-construct]... end-forall-stmt
483 TYPE_CONTEXT_PARSER("FORALL construct"_en_US,
484     construct<ForallConstruct>(statement(Parser<ForallConstructStmt>{}),
485         many(Parser<ForallBodyConstruct>{}),
486         statement(Parser<EndForallStmt>{})))
487 
488 // R1051 forall-construct-stmt ->
489 //         [forall-construct-name :] FORALL concurrent-header
490 TYPE_CONTEXT_PARSER("FORALL construct statement"_en_US,
491     construct<ForallConstructStmt>(
492         maybe(name / ":"), "FORALL" >> indirect(concurrentHeader)))
493 
494 // R1052 forall-body-construct ->
495 //         forall-assignment-stmt | where-stmt | where-construct |
496 //         forall-construct | forall-stmt
497 TYPE_PARSER(construct<ForallBodyConstruct>(statement(forallAssignmentStmt)) ||
498     construct<ForallBodyConstruct>(statement(whereStmt)) ||
499     construct<ForallBodyConstruct>(whereConstruct) ||
500     construct<ForallBodyConstruct>(indirect(forallConstruct)) ||
501     construct<ForallBodyConstruct>(statement(forallStmt)))
502 
503 // R1053 forall-assignment-stmt -> assignment-stmt | pointer-assignment-stmt
504 TYPE_PARSER(construct<ForallAssignmentStmt>(assignmentStmt) ||
505     construct<ForallAssignmentStmt>(pointerAssignmentStmt))
506 
507 // R1054 end-forall-stmt -> END FORALL [forall-construct-name]
508 TYPE_CONTEXT_PARSER("END FORALL statement"_en_US,
509     construct<EndForallStmt>(
510         recovery("END FORALL" >> maybe(name), endStmtErrorRecovery)))
511 
512 // R1055 forall-stmt -> FORALL concurrent-header forall-assignment-stmt
513 TYPE_CONTEXT_PARSER("FORALL statement"_en_US,
514     construct<ForallStmt>("FORALL" >> indirect(concurrentHeader),
515         unlabeledStatement(forallAssignmentStmt)))
516 } // namespace Fortran::parser
517