Index: pkg/front_end/lib/src/fasta/parser/parser.dart |
diff --git a/pkg/front_end/lib/src/fasta/parser/parser.dart b/pkg/front_end/lib/src/fasta/parser/parser.dart |
index 365b7979d7573c7375bccbbc8cb521435273c237..d3bcceae05a50ab1bdd7ec486e4fa479814c7fa3 100644 |
--- a/pkg/front_end/lib/src/fasta/parser/parser.dart |
+++ b/pkg/front_end/lib/src/fasta/parser/parser.dart |
@@ -20,6 +20,8 @@ import '../../scanner/token.dart' |
EQUALITY_PRECEDENCE, |
POSTFIX_PRECEDENCE, |
RELATIONAL_PRECEDENCE, |
+ SyntheticStringToken, |
+ SyntheticToken, |
TokenType; |
import '../scanner/token.dart' show isUserDefinableOperator; |
@@ -68,6 +70,8 @@ import 'listener.dart' show Listener; |
import 'member_kind.dart' show MemberKind; |
+import 'token_stream_rewriter.dart'; |
+ |
import 'type_continuation.dart' |
show TypeContinuation, typeContiunationFromFormalParameterKind; |
@@ -208,6 +212,24 @@ class Parser { |
/// external clients, for example, to parse an expression outside a function. |
AsyncModifier asyncState = AsyncModifier.Sync; |
+ /// The first token in the parse stream and used during parser recovery. |
+ /// This is automatically set by the [parseUnit] method, |
+ /// but must be manually set when any other parse method is called. |
+ /// If not set, then the parser will call [handleUnrecoverableError] |
+ /// rather than rewriting the token stream |
+ /// and calling [handleRecoverableError]. |
+ Token firstToken; |
+ |
+ /// A rewriter for inserting synthetic tokens. |
+ /// Access using [rewriter] for lazy initialization. |
+ TokenStreamRewriter cachedRewriter; |
+ |
+ TokenStreamRewriter get rewriter { |
+ assert(firstToken != null, 'firstToken must be set for parser recovery'); |
+ cachedRewriter ??= new TokenStreamRewriter(firstToken); |
+ return cachedRewriter; |
+ } |
+ |
Parser(this.listener); |
bool get inGenerator { |
@@ -223,6 +245,7 @@ class Parser { |
bool get inPlainSync => asyncState == AsyncModifier.Sync; |
Token parseUnit(Token token) { |
+ firstToken = token; |
listener.beginCompilationUnit(token); |
int count = 0; |
while (!identical(token.kind, EOF_TOKEN)) { |
@@ -230,6 +253,9 @@ class Parser { |
count++; |
} |
listener.endCompilationUnit(count, token); |
+ // Clear fields that could lead to memory leak. |
+ firstToken = null; |
+ cachedRewriter = null; |
return token; |
} |
@@ -356,13 +382,12 @@ class Parser { |
Token exportKeyword = token; |
listener.beginExport(exportKeyword); |
assert(optional('export', token)); |
- token = parseLiteralStringOrRecoverExpression(token.next); |
+ token = ensureParseLiteralString(token.next); |
token = parseConditionalUris(token); |
token = parseCombinators(token); |
- Token semicolon = token; |
- token = expect(';', token); |
+ Token semicolon = ensureSemicolon(token); |
listener.endExport(exportKeyword, semicolon); |
- return token; |
+ return semicolon.next; |
} |
Token parseCombinators(Token token) { |
@@ -909,6 +934,8 @@ class Parser { |
} |
Token expect(String string, Token token) { |
+ // TODO(danrubel) update all uses of expect(';'...) to ensureSemicolon |
+ // then add assert(!identical(';', string)); |
if (!identical(string, token.stringValue)) { |
return reportUnrecoverableError( |
token, fasta.templateExpectedButGot.withArguments(string)) |
@@ -1927,6 +1954,33 @@ class Parser { |
return token; |
} |
+ Token ensureParseLiteralString(Token token) { |
+ if (!identical(token.kind, STRING_TOKEN)) { |
+ Message message = fasta.templateExpectedString.withArguments(token); |
+ Token newToken = |
+ new SyntheticStringToken(TokenType.STRING, '""', token.charOffset, 0); |
+ token = rewriteAndRecover(token, message, newToken); |
+ } |
+ return parseLiteralString(token); |
+ } |
+ |
+ Token ensureSemicolon(Token token) { |
+ // TODO(danrubel): Once all expect(';'...) call sites have been converted |
+ // to use this method, remove similar semicolon recovery code |
+ // from the handleError method in element_listener.dart. |
+ if (optional(';', token)) return token; |
+ Message message = fasta.templateExpectedButGot.withArguments(';'); |
+ Token newToken = new SyntheticToken(TokenType.SEMICOLON, token.charOffset); |
+ return rewriteAndRecover(token, message, newToken); |
+ } |
+ |
+ Token rewriteAndRecover(Token token, Message message, Token newToken) { |
+ if (firstToken == null) return reportUnrecoverableError(token, message); |
+ reportRecoverableError(token, message); |
+ token = rewriter.insertTokenBefore(newToken, token); |
+ return token; |
+ } |
+ |
Token parseLiteralStringOrRecoverExpression(Token token) { |
if (identical(token.kind, STRING_TOKEN)) { |
return parseLiteralString(token); |