Skip to content

Commit 977e75c

Browse files
authored
[flang] More graceful parse failures at EOF (#159834)
Rather than admit complete confusion with a "FAIL" message when parsing runs into the end of the source file unexpectedly, deal better with missing or garbled END statements through the use of `consumedAllInput` in end-of-line and end-of-statement parsers and error recovery. Adds a new test (the original motivation) and updates the expected results in two extant tests that were failing before.
1 parent c7a4163 commit 977e75c

File tree

7 files changed

+57
-31
lines changed

7 files changed

+57
-31
lines changed

flang/docs/ParserCombinators.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ These objects and functions are (or return) the fundamental parsers:
6363
the value that the parser never returns.
6464
* `nextCh` consumes the next character and returns its location,
6565
and fails at EOF.
66+
* `consumedAllInput` is equivalent, but preferable, to `!nextCh`.
6667
* `"xyz"_ch` succeeds if the next character consumed matches any of those
6768
in the string and returns its location. Be advised that the source
6869
will have been normalized to lower case (miniscule) letters outside

flang/lib/Parser/executable-parsers.cpp

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,26 @@ constexpr auto obsoleteExecutionPartConstruct{recovery(ignoredStatementPrefix >>
6565
statement("REDIMENSION" >> name /
6666
parenthesized(nonemptyList(Parser<AllocateShapeSpec>{}))))))};
6767

68-
TYPE_PARSER(recovery(
69-
CONTEXT_PARSER("execution part construct"_en_US,
70-
first(construct<ExecutionPartConstruct>(executableConstruct),
71-
construct<ExecutionPartConstruct>(statement(indirect(formatStmt))),
72-
construct<ExecutionPartConstruct>(statement(indirect(entryStmt))),
73-
construct<ExecutionPartConstruct>(statement(indirect(dataStmt))),
74-
extension<LanguageFeature::ExecutionPartNamelist>(
75-
"nonstandard usage: NAMELIST in execution part"_port_en_US,
68+
// The "!consumedAllInput >>" test prevents a cascade of errors at EOF.
69+
TYPE_PARSER(!consumedAllInput >>
70+
recovery(
71+
CONTEXT_PARSER("execution part construct"_en_US,
72+
first(construct<ExecutionPartConstruct>(executableConstruct),
7673
construct<ExecutionPartConstruct>(
77-
statement(indirect(Parser<NamelistStmt>{})))),
78-
obsoleteExecutionPartConstruct,
79-
lookAhead(declarationConstruct) >> SkipTo<'\n'>{} >>
80-
fail<ExecutionPartConstruct>(
81-
"misplaced declaration in the execution part"_err_en_US))),
82-
construct<ExecutionPartConstruct>(executionPartErrorRecovery)))
74+
statement(indirect(formatStmt))),
75+
construct<ExecutionPartConstruct>(
76+
statement(indirect(entryStmt))),
77+
construct<ExecutionPartConstruct>(
78+
statement(indirect(dataStmt))),
79+
extension<LanguageFeature::ExecutionPartNamelist>(
80+
"nonstandard usage: NAMELIST in execution part"_port_en_US,
81+
construct<ExecutionPartConstruct>(
82+
statement(indirect(Parser<NamelistStmt>{})))),
83+
obsoleteExecutionPartConstruct,
84+
lookAhead(declarationConstruct) >> SkipTo<'\n'>{} >>
85+
fail<ExecutionPartConstruct>(
86+
"misplaced declaration in the execution part"_err_en_US))),
87+
construct<ExecutionPartConstruct>(executionPartErrorRecovery)))
8388

8489
// R509 execution-part -> executable-construct [execution-part-construct]...
8590
TYPE_CONTEXT_PARSER("execution part"_en_US,

flang/lib/Parser/program-parsers.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,11 @@ static constexpr auto programUnit{
6767
lookAhead(maybe(label) >> validFunctionStmt) >>
6868
construct<ProgramUnit>(indirect(functionSubprogram)) ||
6969
construct<ProgramUnit>(indirect(Parser<MainProgram>{}))};
70-
static constexpr auto normalProgramUnit{StartNewSubprogram{} >> programUnit /
71-
skipMany(";"_tok) / space / recovery(endOfLine, SkipPast<'\n'>{})};
70+
71+
static constexpr auto normalProgramUnit{
72+
!consumedAllInput >> StartNewSubprogram{} >> programUnit /
73+
skipMany(";"_tok) / space / recovery(endOfLine, skipToNextLineIfAny)};
74+
7275
static constexpr auto globalCompilerDirective{
7376
construct<ProgramUnit>(indirect(compilerDirective))};
7477

@@ -86,7 +89,7 @@ static constexpr auto globalOpenACCCompilerDirective{
8689
TYPE_PARSER(
8790
construct<Program>(extension<LanguageFeature::EmptySourceFile>(
8891
"nonstandard usage: empty source file"_port_en_US,
89-
skipStuffBeforeStatement >> !nextCh >>
92+
skipStuffBeforeStatement >> consumedAllInput >>
9093
pure<std::list<ProgramUnit>>()) ||
9194
some(globalCompilerDirective || globalOpenACCCompilerDirective ||
9295
normalProgramUnit) /
@@ -107,7 +110,7 @@ constexpr auto actionStmtLookAhead{first(actionStmt >> ok,
107110
// first in the execution part
108111
"ALLOCATE ("_tok, "CALL" >> name >> "("_tok, "GO TO"_tok, "OPEN ("_tok,
109112
"PRINT"_tok / space / !"("_tok, "READ ("_tok, "WRITE ("_tok)};
110-
constexpr auto execPartLookAhead{first(actionStmtLookAhead >> ok,
113+
constexpr auto execPartLookAhead{first(actionStmtLookAhead,
111114
openaccConstruct >> ok, openmpExecDirective >> ok, "ASSOCIATE ("_tok,
112115
"BLOCK"_tok, "SELECT"_tok, "CHANGE TEAM"_sptok, "CRITICAL"_tok, "DO"_tok,
113116
"IF ("_tok, "WHERE ("_tok, "FORALL ("_tok, "!$CUF"_tok)};

flang/lib/Parser/stmt-parser.h

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,22 @@ template <typename PA>
2727
inline constexpr auto unterminatedStatement(const PA &p) {
2828
return skipStuffBeforeStatement >>
2929
sourced(construct<Statement<typename PA::resultType>>(
30-
maybe(label), space >> p));
30+
maybe(label / space), p));
3131
}
3232

3333
constexpr auto atEndOfStmt{space >>
3434
withMessage("expected end of statement"_err_en_US, lookAhead(";\n"_ch))};
3535
constexpr auto checkEndOfKnownStmt{recovery(atEndOfStmt, SkipTo<'\n'>{})};
3636

37-
constexpr auto endOfLine{
38-
"\n"_ch >> ok || fail("expected end of line"_err_en_US)};
37+
constexpr auto endOfLine{consumedAllInput ||
38+
withMessage("expected end of line"_err_en_US, "\n"_ch >> ok)};
3939

4040
constexpr auto semicolons{";"_ch >> skipMany(";"_tok) / space / maybe("\n"_ch)};
4141
constexpr auto endOfStmt{
4242
space >> withMessage("expected end of statement"_err_en_US,
4343
semicolons || endOfLine)};
44-
constexpr auto forceEndOfStmt{recovery(endOfStmt, SkipPast<'\n'>{})};
44+
constexpr auto skipToNextLineIfAny{consumedAllInput || SkipPast<'\n'>{}};
45+
constexpr auto forceEndOfStmt{recovery(endOfStmt, skipToNextLineIfAny)};
4546

4647
template <typename PA> inline constexpr auto statement(const PA &p) {
4748
return unterminatedStatement(p) / endOfStmt;
@@ -70,17 +71,17 @@ constexpr auto ignoredStatementPrefix{
7071
// Error recovery within a statement() call: skip *to* the end of the line,
7172
// unless at an END or CONTAINS statement.
7273
constexpr auto inStmtErrorRecovery{!"END"_tok >> !"CONTAINS"_tok >>
73-
SkipTo<'\n'>{} >> construct<ErrorRecovery>()};
74+
(consumedAllInput || SkipTo<'\n'>{}) >> construct<ErrorRecovery>()};
7475

7576
// Error recovery within statement sequences: skip *past* the end of the line,
7677
// but not over an END or CONTAINS statement.
7778
constexpr auto skipStmtErrorRecovery{!"END"_tok >> !"CONTAINS"_tok >>
78-
SkipPast<'\n'>{} >> construct<ErrorRecovery>()};
79+
(consumedAllInput || SkipPast<'\n'>{}) >> construct<ErrorRecovery>()};
7980

8081
// Error recovery across statements: skip the line, unless it looks
8182
// like it might end the containing construct.
8283
constexpr auto stmtErrorRecoveryStart{ignoredStatementPrefix};
83-
constexpr auto skipBadLine{SkipPast<'\n'>{} >> construct<ErrorRecovery>()};
84+
constexpr auto skipBadLine{skipToNextLineIfAny >> construct<ErrorRecovery>()};
8485
constexpr auto executionPartErrorRecovery{stmtErrorRecoveryStart >>
8586
!"END"_tok >> !"CONTAINS"_tok >> !"ELSE"_tok >> !"CASE"_tok >>
8687
!"TYPE IS"_tok >> !"CLASS"_tok >> !"RANK"_tok >>
@@ -93,7 +94,7 @@ constexpr auto noNameEnd{"END" >> missingOptionalName};
9394

9495
// For unrecognizable construct END statements. Be sure to not consume
9596
// a program unit's END statement.
96-
constexpr auto progUnitEndStmt{
97+
constexpr auto progUnitEndStmt{consumedAllInput ||
9798
"END" >> (lookAhead("\n"_ch) || "SUBROUTINE"_tok || "FUNCTION"_tok ||
9899
"PROCEDURE"_tok || "MODULE"_tok || "SUBMODULE"_tok ||
99100
"PROGRAM"_tok || "BLOCK DATA"_tok)};
@@ -103,9 +104,8 @@ constexpr auto namedConstructEndStmtErrorRecovery{
103104
constructEndStmtErrorRecovery >> missingOptionalName};
104105

105106
constexpr auto progUnitEndStmtErrorRecovery{
106-
(many(!"END"_tok >> SkipPast<'\n'>{}) >>
107-
("END"_tok >> SkipTo<'\n'>{} || consumedAllInput)) >>
108-
missingOptionalName};
107+
many(!"END"_tok >> SkipPast<'\n'>{}) >>
108+
maybe("END"_tok >> SkipTo<'\n'>{}) >> missingOptionalName};
109109

110110
constexpr auto beginDirective{skipStuffBeforeStatement >> "!"_ch};
111111
constexpr auto endDirective{space >> endOfLine};

flang/test/Parser/at-process.f

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ subroutine f()
1919
!CHECK: Character in fixed-form label field must be a digit
2020
@precoss
2121

22-
!CHECK: at-process.f:14:1: error: parser FAIL (final position)
22+
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
!RUN: not %flang_fc1 -fsyntax-only %s 2>&1 | FileCheck %s
2+
!CHECK:come-to-a-bad-end.f90:13:4: error: expected '('
3+
!CHECK: in the context: statement function definition
4+
!CHECK: in the context: SUBROUTINE subprogram
5+
!CHECK:error: expected declaration construct
6+
!CHECK:come-to-a-bad-end.f90:13:1: in the context: specification part
7+
!CHECK: in the context: SUBROUTINE subprogram
8+
!CHECK:error: end of file
9+
!CHECK: in the context: SUBROUTINE subprogram
10+
subroutine a
11+
end
12+
subroutine b
13+
gnd

flang/test/Parser/unparseable.f90

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
! RUN: not %flang_fc1 -fsyntax-only %s 2>&1 | FileCheck %s
2-
! CHECK: unparseable.f90:5:1: error: parser FAIL (final position)
2+
! CHECK:error: end of file
3+
! CHECK:in the context: END PROGRAM statement
4+
! CHECK:unparseable.f90:9:1: in the context: main program
5+
! CHECK:error: end of file
6+
! CHECK:unparseable.f90:9:1: in the context: SELECT TYPE construct
37
module m
48
end
59
select type (barf)

0 commit comments

Comments
 (0)