1 //===-- lib/Parser/expr-parsers.cpp ---------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 // Per-type parsers for expressions.
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"
21 namespace Fortran::parser
{
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
{}))
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
>{})))
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
/ "::"))
43 // R773 ac-value -> expr | ac-implied-do
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
>{})))
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
>{})))
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
)))
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
)))))))};
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
)))))};
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.
98 using resultType
= Expr
;
99 constexpr MultOperand() {}
100 static inline std::optional
<Expr
> Parse(ParseState
&);
103 static constexpr auto multOperand
{sourced(MultOperand
{})};
105 inline std::optional
<Expr
> MultOperand::Parse(ParseState
&state
) {
106 std::optional
<Expr
> result
{level1Expr
.Parse(state
)};
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
))};
113 return applyLambda(power
, multOperand
).Parse(state
); // right-recursive
119 // R1005 add-operand -> [add-operand mult-op] mult-operand
120 // R1008 mult-op -> * | /
121 // The left recursion in the grammar is implemented iteratively.
123 using resultType
= Expr
;
124 constexpr AddOperand() {}
125 static inline std::optional
<Expr
> Parse(ParseState
&state
) {
126 std::optional
<Expr
> result
{multOperand
.Parse(state
)};
128 auto source
{result
->source
};
129 std::function
<Expr(Expr
&&)> multiply
{[&result
](Expr
&&right
) {
131 Expr::Multiply(std::move(result
).value(), std::move(right
))};
133 std::function
<Expr(Expr
&&)> divide
{[&result
](Expr
&&right
) {
134 return Expr
{Expr::Divide(std::move(result
).value(), std::move(right
))};
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
);
146 constexpr AddOperand addOperand
;
148 // R1006 level-2-expr -> [[level-2-expr] add-op] add-operand
149 // R1009 add-op -> + | -
150 // These are left-recursive productions, implemented iteratively.
151 // Note that standard Fortran admits a unary + or - to appear only here,
152 // by means of a missing first operand; e.g., 2*-3 is valid in C but not
153 // standard Fortran. We accept unary + and - to appear before any primary
156 using resultType
= Expr
;
157 constexpr Level2Expr() {}
158 static inline std::optional
<Expr
> Parse(ParseState
&state
) {
159 static constexpr auto unary
{
161 construct
<Expr
>(construct
<Expr::UnaryPlus
>("+" >> addOperand
)) ||
162 construct
<Expr
>(construct
<Expr::Negate
>("-" >> addOperand
))) ||
164 std::optional
<Expr
> result
{unary
.Parse(state
)};
166 auto source
{result
->source
};
167 std::function
<Expr(Expr
&&)> add
{[&result
](Expr
&&right
) {
168 return Expr
{Expr::Add(std::move(result
).value(), std::move(right
))};
170 std::function
<Expr(Expr
&&)> subtract
{[&result
](Expr
&&right
) {
172 Expr::Subtract(std::move(result
).value(), std::move(right
))};
174 auto more
{attempt(sourced("+" >> applyLambda(add
, addOperand
) ||
175 "-" >> applyLambda(subtract
, addOperand
)))};
176 while (std::optional
<Expr
> next
{more
.Parse(state
)}) {
177 result
= std::move(next
);
178 result
->source
.ExtendToCover(source
);
184 constexpr Level2Expr level2Expr
;
186 // R1010 level-3-expr -> [level-3-expr concat-op] level-2-expr
187 // R1011 concat-op -> //
188 // Concatenation (//) is left-associative for parsing performance, although
189 // one would never notice if it were right-associated.
191 using resultType
= Expr
;
192 constexpr Level3Expr() {}
193 static inline std::optional
<Expr
> Parse(ParseState
&state
) {
194 std::optional
<Expr
> result
{level2Expr
.Parse(state
)};
196 auto source
{result
->source
};
197 std::function
<Expr(Expr
&&)> concat
{[&result
](Expr
&&right
) {
198 return Expr
{Expr::Concat(std::move(result
).value(), std::move(right
))};
200 auto more
{attempt(sourced("//" >> applyLambda(concat
, level2Expr
)))};
201 while (std::optional
<Expr
> next
{more
.Parse(state
)}) {
202 result
= std::move(next
);
203 result
->source
.ExtendToCover(source
);
209 constexpr Level3Expr level3Expr
;
211 // R1012 level-4-expr -> [level-3-expr rel-op] level-3-expr
213 // .EQ. | .NE. | .LT. | .LE. | .GT. | .GE. |
214 // == | /= | < | <= | > | >= @ | <>
215 // N.B. relations are not recursive (i.e., LOGICAL is not ordered)
217 using resultType
= Expr
;
218 constexpr Level4Expr() {}
219 static inline std::optional
<Expr
> Parse(ParseState
&state
) {
220 std::optional
<Expr
> result
{level3Expr
.Parse(state
)};
222 auto source
{result
->source
};
223 std::function
<Expr(Expr
&&)> lt
{[&result
](Expr
&&right
) {
224 return Expr
{Expr::LT(std::move(result
).value(), std::move(right
))};
226 std::function
<Expr(Expr
&&)> le
{[&result
](Expr
&&right
) {
227 return Expr
{Expr::LE(std::move(result
).value(), std::move(right
))};
229 std::function
<Expr(Expr
&&)> eq
{[&result
](Expr
&&right
) {
230 return Expr
{Expr::EQ(std::move(result
).value(), std::move(right
))};
232 std::function
<Expr(Expr
&&)> ne
{[&result
](Expr
&&right
) {
233 return Expr
{Expr::NE(std::move(result
).value(), std::move(right
))};
235 std::function
<Expr(Expr
&&)> ge
{[&result
](Expr
&&right
) {
236 return Expr
{Expr::GE(std::move(result
).value(), std::move(right
))};
238 std::function
<Expr(Expr
&&)> gt
{[&result
](Expr
&&right
) {
239 return Expr
{Expr::GT(std::move(result
).value(), std::move(right
))};
242 sourced((".LT."_tok
|| "<"_tok
) >> applyLambda(lt
, level3Expr
) ||
243 (".LE."_tok
|| "<="_tok
) >> applyLambda(le
, level3Expr
) ||
244 (".EQ."_tok
|| "=="_tok
) >> applyLambda(eq
, level3Expr
) ||
245 (".NE."_tok
|| "/="_tok
||
246 extension
<LanguageFeature::AlternativeNE
>(
247 "<>"_tok
/* PGI/Cray extension; Cray also has .LG. */)) >>
248 applyLambda(ne
, level3Expr
) ||
249 (".GE."_tok
|| ">="_tok
) >> applyLambda(ge
, level3Expr
) ||
250 (".GT."_tok
|| ">"_tok
) >> applyLambda(gt
, level3Expr
)))};
251 if (std::optional
<Expr
> next
{more
.Parse(state
)}) {
252 next
->source
.ExtendToCover(source
);
259 constexpr Level4Expr level4Expr
;
261 // R1014 and-operand -> [not-op] level-4-expr
262 // R1018 not-op -> .NOT.
263 // N.B. Fortran's .NOT. binds less tightly than its comparison operators do.
264 // PGI/Intel extension: accept multiple .NOT. operators
266 using resultType
= Expr
;
267 constexpr AndOperand() {}
268 static inline std::optional
<Expr
> Parse(ParseState
&);
270 constexpr AndOperand andOperand
;
272 // Match a logical operator or, optionally, its abbreviation.
273 inline constexpr auto logicalOp(const char *op
, const char *abbrev
) {
274 return TokenStringMatch
{op
} ||
275 extension
<LanguageFeature::LogicalAbbreviations
>(
276 TokenStringMatch
{abbrev
});
279 inline std::optional
<Expr
> AndOperand::Parse(ParseState
&state
) {
280 static constexpr auto notOp
{attempt(logicalOp(".NOT.", ".N.") >> andOperand
)};
281 if (std::optional
<Expr
> negation
{notOp
.Parse(state
)}) {
282 return Expr
{Expr::NOT
{std::move(*negation
)}};
284 return level4Expr
.Parse(state
);
288 // R1015 or-operand -> [or-operand and-op] and-operand
289 // R1019 and-op -> .AND.
290 // .AND. is left-associative
292 using resultType
= Expr
;
293 constexpr OrOperand() {}
294 static inline std::optional
<Expr
> Parse(ParseState
&state
) {
295 static constexpr auto operand
{sourced(andOperand
)};
296 std::optional
<Expr
> result
{operand
.Parse(state
)};
298 auto source
{result
->source
};
299 std::function
<Expr(Expr
&&)> logicalAnd
{[&result
](Expr
&&right
) {
300 return Expr
{Expr::AND(std::move(result
).value(), std::move(right
))};
302 auto more
{attempt(sourced(
303 logicalOp(".AND.", ".A.") >> applyLambda(logicalAnd
, andOperand
)))};
304 while (std::optional
<Expr
> next
{more
.Parse(state
)}) {
305 result
= std::move(next
);
306 result
->source
.ExtendToCover(source
);
312 constexpr OrOperand orOperand
;
314 // R1016 equiv-operand -> [equiv-operand or-op] or-operand
315 // R1020 or-op -> .OR.
316 // .OR. is left-associative
317 struct EquivOperand
{
318 using resultType
= Expr
;
319 constexpr EquivOperand() {}
320 static inline std::optional
<Expr
> Parse(ParseState
&state
) {
321 std::optional
<Expr
> result
{orOperand
.Parse(state
)};
323 auto source
{result
->source
};
324 std::function
<Expr(Expr
&&)> logicalOr
{[&result
](Expr
&&right
) {
325 return Expr
{Expr::OR(std::move(result
).value(), std::move(right
))};
327 auto more
{attempt(sourced(
328 logicalOp(".OR.", ".O.") >> applyLambda(logicalOr
, orOperand
)))};
329 while (std::optional
<Expr
> next
{more
.Parse(state
)}) {
330 result
= std::move(next
);
331 result
->source
.ExtendToCover(source
);
337 constexpr EquivOperand equivOperand
;
339 // R1017 level-5-expr -> [level-5-expr equiv-op] equiv-operand
340 // R1021 equiv-op -> .EQV. | .NEQV.
341 // Logical equivalence is left-associative.
342 // Extension: .XOR. as synonym for .NEQV.
344 using resultType
= Expr
;
345 constexpr Level5Expr() {}
346 static inline std::optional
<Expr
> Parse(ParseState
&state
) {
347 std::optional
<Expr
> result
{equivOperand
.Parse(state
)};
349 auto source
{result
->source
};
350 std::function
<Expr(Expr
&&)> eqv
{[&result
](Expr
&&right
) {
351 return Expr
{Expr::EQV(std::move(result
).value(), std::move(right
))};
353 std::function
<Expr(Expr
&&)> neqv
{[&result
](Expr
&&right
) {
354 return Expr
{Expr::NEQV(std::move(result
).value(), std::move(right
))};
356 auto more
{attempt(sourced(".EQV." >> applyLambda(eqv
, equivOperand
) ||
358 extension
<LanguageFeature::XOROperator
>(
359 logicalOp(".XOR.", ".X."))) >>
360 applyLambda(neqv
, equivOperand
)))};
361 while (std::optional
<Expr
> next
{more
.Parse(state
)}) {
362 result
= std::move(next
);
363 result
->source
.ExtendToCover(source
);
369 constexpr Level5Expr level5Expr
;
371 // R1022 expr -> [expr defined-binary-op] level-5-expr
372 // Defined binary operators associate leftwards.
373 template <> std::optional
<Expr
> Parser
<Expr
>::Parse(ParseState
&state
) {
374 std::optional
<Expr
> result
{level5Expr
.Parse(state
)};
376 auto source
{result
->source
};
377 std::function
<Expr(DefinedOpName
&&, Expr
&&)> defBinOp
{
378 [&result
](DefinedOpName
&&op
, Expr
&&right
) {
379 return Expr
{Expr::DefinedBinary(
380 std::move(op
), std::move(result
).value(), std::move(right
))};
383 sourced(applyLambda
<Expr
>(defBinOp
, definedOpName
, level5Expr
)))};
384 while (std::optional
<Expr
> next
{more
.Parse(state
)}) {
385 result
= std::move(next
);
386 result
->source
.ExtendToCover(source
);
392 // R1003 defined-unary-op -> . letter [letter]... .
393 // R1023 defined-binary-op -> . letter [letter]... .
394 // R1414 local-defined-operator -> defined-unary-op | defined-binary-op
395 // R1415 use-defined-operator -> defined-unary-op | defined-binary-op
396 // C1003 A defined operator must be distinct from logical literal constants
397 // and intrinsic operator names; this is handled by attempting their parses
398 // first, and by name resolution on their definitions, for best errors.
399 // N.B. The name of the operator is captured with the dots around it.
400 constexpr auto definedOpNameChar
{
401 letter
|| extension
<LanguageFeature::PunctuationInNames
>("$@"_ch
)};
403 space
>> construct
<DefinedOpName
>(sourced("."_ch
>>
404 some(definedOpNameChar
) >> construct
<Name
>() / "."_ch
)))
406 // R1028 specification-expr -> scalar-int-expr
407 TYPE_PARSER(construct
<SpecificationExpr
>(scalarIntExpr
))
409 // R1032 assignment-stmt -> variable = expr
410 TYPE_CONTEXT_PARSER("assignment statement"_en_US
,
411 construct
<AssignmentStmt
>(variable
/ "=", expr
))
413 // R1033 pointer-assignment-stmt ->
414 // data-pointer-object [( bounds-spec-list )] => data-target |
415 // data-pointer-object ( bounds-remapping-list ) => data-target |
416 // proc-pointer-object => proc-target
417 // R1034 data-pointer-object ->
418 // variable-name | scalar-variable % data-pointer-component-name
419 // C1022 a scalar-variable shall be a data-ref
420 // C1024 a data-pointer-object shall not be a coindexed object
421 // R1038 proc-pointer-object -> proc-pointer-name | proc-component-ref
423 // A distinction can't be made at the time of the initial parse between
424 // data-pointer-object and proc-pointer-object, or between data-target
426 TYPE_CONTEXT_PARSER("pointer assignment statement"_en_US
,
427 construct
<PointerAssignmentStmt
>(dataRef
,
428 parenthesized(nonemptyList(Parser
<BoundsRemapping
>{})), "=>" >> expr
) ||
429 construct
<PointerAssignmentStmt
>(dataRef
,
430 defaulted(parenthesized(nonemptyList(Parser
<BoundsSpec
>{}))),
433 // R1035 bounds-spec -> lower-bound-expr :
434 TYPE_PARSER(construct
<BoundsSpec
>(boundExpr
/ ":"))
436 // R1036 bounds-remapping -> lower-bound-expr : upper-bound-expr
437 TYPE_PARSER(construct
<BoundsRemapping
>(boundExpr
/ ":", boundExpr
))
439 // R1039 proc-component-ref -> scalar-variable % procedure-component-name
440 // C1027 the scalar-variable must be a data-ref without coindices.
441 TYPE_PARSER(construct
<ProcComponentRef
>(structureComponent
))
443 // R1041 where-stmt -> WHERE ( mask-expr ) where-assignment-stmt
444 // R1045 where-assignment-stmt -> assignment-stmt
445 // R1046 mask-expr -> logical-expr
446 TYPE_CONTEXT_PARSER("WHERE statement"_en_US
,
447 construct
<WhereStmt
>("WHERE" >> parenthesized(logicalExpr
), assignmentStmt
))
449 // R1042 where-construct ->
450 // where-construct-stmt [where-body-construct]...
451 // [masked-elsewhere-stmt [where-body-construct]...]...
452 // [elsewhere-stmt [where-body-construct]...] end-where-stmt
453 TYPE_CONTEXT_PARSER("WHERE construct"_en_US
,
454 construct
<WhereConstruct
>(statement(Parser
<WhereConstructStmt
>{}),
455 many(whereBodyConstruct
),
456 many(construct
<WhereConstruct::MaskedElsewhere
>(
457 statement(Parser
<MaskedElsewhereStmt
>{}),
458 many(whereBodyConstruct
))),
459 maybe(construct
<WhereConstruct::Elsewhere
>(
460 statement(Parser
<ElsewhereStmt
>{}), many(whereBodyConstruct
))),
461 statement(Parser
<EndWhereStmt
>{})))
463 // R1043 where-construct-stmt -> [where-construct-name :] WHERE ( mask-expr )
464 TYPE_CONTEXT_PARSER("WHERE construct statement"_en_US
,
465 construct
<WhereConstructStmt
>(
466 maybe(name
/ ":"), "WHERE" >> parenthesized(logicalExpr
)))
468 // R1044 where-body-construct ->
469 // where-assignment-stmt | where-stmt | where-construct
470 TYPE_PARSER(construct
<WhereBodyConstruct
>(statement(assignmentStmt
)) ||
471 construct
<WhereBodyConstruct
>(statement(whereStmt
)) ||
472 construct
<WhereBodyConstruct
>(indirect(whereConstruct
)))
474 // R1047 masked-elsewhere-stmt ->
475 // ELSEWHERE ( mask-expr ) [where-construct-name]
476 TYPE_CONTEXT_PARSER("masked ELSEWHERE statement"_en_US
,
477 construct
<MaskedElsewhereStmt
>(
478 "ELSE WHERE" >> parenthesized(logicalExpr
), maybe(name
)))
480 // R1048 elsewhere-stmt -> ELSEWHERE [where-construct-name]
481 TYPE_CONTEXT_PARSER("ELSEWHERE statement"_en_US
,
482 construct
<ElsewhereStmt
>("ELSE WHERE" >> maybe(name
)))
484 // R1049 end-where-stmt -> ENDWHERE [where-construct-name]
485 TYPE_CONTEXT_PARSER("END WHERE statement"_en_US
,
486 construct
<EndWhereStmt
>(
487 recovery("END WHERE" >> maybe(name
), endStmtErrorRecovery
)))
489 // R1050 forall-construct ->
490 // forall-construct-stmt [forall-body-construct]... end-forall-stmt
491 TYPE_CONTEXT_PARSER("FORALL construct"_en_US
,
492 construct
<ForallConstruct
>(statement(Parser
<ForallConstructStmt
>{}),
493 many(Parser
<ForallBodyConstruct
>{}),
494 statement(Parser
<EndForallStmt
>{})))
496 // R1051 forall-construct-stmt ->
497 // [forall-construct-name :] FORALL concurrent-header
498 TYPE_CONTEXT_PARSER("FORALL construct statement"_en_US
,
499 construct
<ForallConstructStmt
>(
500 maybe(name
/ ":"), "FORALL" >> indirect(concurrentHeader
)))
502 // R1052 forall-body-construct ->
503 // forall-assignment-stmt | where-stmt | where-construct |
504 // forall-construct | forall-stmt
505 TYPE_PARSER(construct
<ForallBodyConstruct
>(statement(forallAssignmentStmt
)) ||
506 construct
<ForallBodyConstruct
>(statement(whereStmt
)) ||
507 construct
<ForallBodyConstruct
>(whereConstruct
) ||
508 construct
<ForallBodyConstruct
>(indirect(forallConstruct
)) ||
509 construct
<ForallBodyConstruct
>(statement(forallStmt
)))
511 // R1053 forall-assignment-stmt -> assignment-stmt | pointer-assignment-stmt
512 TYPE_PARSER(construct
<ForallAssignmentStmt
>(assignmentStmt
) ||
513 construct
<ForallAssignmentStmt
>(pointerAssignmentStmt
))
515 // R1054 end-forall-stmt -> END FORALL [forall-construct-name]
516 TYPE_CONTEXT_PARSER("END FORALL statement"_en_US
,
517 construct
<EndForallStmt
>(
518 recovery("END FORALL" >> maybe(name
), endStmtErrorRecovery
)))
520 // R1055 forall-stmt -> FORALL concurrent-header forall-assignment-stmt
521 TYPE_CONTEXT_PARSER("FORALL statement"_en_US
,
522 construct
<ForallStmt
>("FORALL" >> indirect(concurrentHeader
),
523 unlabeledStatement(forallAssignmentStmt
)))
524 } // namespace Fortran::parser