Files
sdk/pkg/front_end/lib/src/fasta/parser/parser.dart
T
Johnni Winther d76ffbc149 [cfe] Handle assigned variables in for and for-in
Change-Id: I06deda7f017dcd40e5c6987c61261cf3be2be0ff
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/123410
Reviewed-by: Dmitry Stefantsov <dmitryas@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
2019-10-31 11:36:43 +00:00

7047 lines
240 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library fasta.parser.parser;
import '../fasta_codes.dart' show Message, Template;
import '../fasta_codes.dart' as fasta;
import '../scanner.dart' show ErrorToken, Token;
import '../../scanner/token.dart'
show
ASSIGNMENT_PRECEDENCE,
BeginToken,
CASCADE_PRECEDENCE,
EQUALITY_PRECEDENCE,
Keyword,
POSTFIX_PRECEDENCE,
RELATIONAL_PRECEDENCE,
SELECTOR_PRECEDENCE,
SyntheticBeginToken,
SyntheticKeywordToken,
SyntheticStringToken,
SyntheticToken,
TokenType;
import '../scanner/token_constants.dart'
show
BANG_EQ_EQ_TOKEN,
COMMA_TOKEN,
DOUBLE_TOKEN,
EOF_TOKEN,
EQ_EQ_EQ_TOKEN,
EQ_TOKEN,
FUNCTION_TOKEN,
HASH_TOKEN,
HEXADECIMAL_TOKEN,
IDENTIFIER_TOKEN,
INT_TOKEN,
KEYWORD_TOKEN,
LT_TOKEN,
OPEN_CURLY_BRACKET_TOKEN,
OPEN_PAREN_TOKEN,
OPEN_SQUARE_BRACKET_TOKEN,
SEMICOLON_TOKEN,
STRING_INTERPOLATION_IDENTIFIER_TOKEN,
STRING_INTERPOLATION_TOKEN,
STRING_TOKEN;
import 'assert.dart' show Assert;
import 'async_modifier.dart' show AsyncModifier;
import 'block_kind.dart';
import 'declaration_kind.dart' show DeclarationKind;
import 'directive_context.dart';
import 'formal_parameter_kind.dart'
show
FormalParameterKind,
isMandatoryFormalParameterKind,
isOptionalPositionalFormalParameterKind;
import 'forwarding_listener.dart' show ForwardingListener;
import 'identifier_context.dart'
show IdentifierContext, looksLikeExpressionStart;
import 'listener.dart' show Listener;
import 'literal_entry_info.dart'
show
LiteralEntryInfo,
computeLiteralEntry,
looksLikeLiteralEntry,
simpleEntry;
import 'loop_state.dart' show LoopState;
import 'member_kind.dart' show MemberKind;
import 'modifier_context.dart' show ModifierRecoveryContext, isModifier;
import 'recovery_listeners.dart'
show
ClassHeaderRecoveryListener,
ImportRecoveryListener,
MixinHeaderRecoveryListener;
import 'token_stream_rewriter.dart' show TokenStreamRewriter;
import 'type_info.dart'
show
TypeInfo,
TypeParamOrArgInfo,
computeMethodTypeArguments,
computeType,
computeTypeParamOrArg,
isValidTypeReference,
noType,
noTypeParamOrArg;
import 'util.dart'
show
findNonZeroLengthToken,
findPreviousNonZeroLengthToken,
isLetter,
isLetterOrDigit,
isOneOf,
isOneOfOrEof,
isWhitespace,
optional;
/// An event generating parser of Dart programs. This parser expects all tokens
/// in a linked list (aka a token stream).
///
/// The class [Scanner] is used to generate a token stream. See the file
/// [scanner.dart](../scanner.dart).
///
/// Subclasses of the class [Listener] are used to listen to events.
///
/// Most methods of this class belong in one of four major categories: parse
/// methods, peek methods, ensure methods, and skip methods.
///
/// Parse methods all have the prefix `parse`, generate events
/// (by calling methods on [listener]), and return the next token to parse.
/// Some exceptions to this last point are methods such as [parseFunctionBody]
/// and [parseClassOrMixinOrExtensionBody] which return the last token parsed
/// rather than the next token to be parsed.
/// Parse methods are generally named `parseGrammarProductionSuffix`.
/// The suffix can be one of `opt`, or `star`.
/// `opt` means zero or one matches, `star` means zero or more matches.
/// For example, [parseMetadataStar] corresponds to this grammar snippet:
/// `metadata*`, and [parseArgumentsOpt] corresponds to: `arguments?`.
///
/// Peek methods all have the prefix `peek`, do not generate events
/// (except for errors) and may return null.
///
/// Ensure methods all have the prefix `ensure` and may generate events.
/// They return the current token, or insert and return a synthetic token
/// if the current token does not match. For example,
/// [ensureSemicolon] returns the current token if the current token is a
/// semicolon, otherwise inserts a synthetic semicolon in the token stream
/// before the current token and then returns that new synthetic token.
///
/// Skip methods are like parse methods, but all have the prefix `skip`
/// and skip over some parts of the file being parsed.
/// Typically, skip methods generate an event for the structure being skipped,
/// but not for its substructures.
///
/// ## Current Token
///
/// The current token is always to be found in a formal parameter named
/// `token`. This parameter should be the first as this increases the chance
/// that a compiler will place it in a register.
///
/// ## Implementation Notes
///
/// The parser assumes that keywords, built-in identifiers, and other special
/// words (pseudo-keywords) are all canonicalized. To extend the parser to
/// recognize a new identifier, one should modify
/// [keyword.dart](../scanner/keyword.dart) and ensure the identifier is added
/// to the keyword table.
///
/// As a consequence of this, one should not use `==` to compare strings in the
/// parser. One should favor the methods [optional] and [expect] to recognize
/// keywords or identifiers. In some cases, it's possible to compare a token's
/// `stringValue` using [identical], but normally [optional] will suffice.
///
/// Historically, we over-used identical, and when identical is used on objects
/// other than strings, it can often be replaced by `==`.
///
/// ## Flexibility, Extensibility, and Specification
///
/// The parser is designed to be flexible and extensible. Its methods are
/// designed to be overridden in subclasses, so it can be extended to handle
/// unspecified language extension or experiments while everything in this file
/// attempts to follow the specification (unless when it interferes with error
/// recovery).
///
/// We achieve flexibility, extensible, and specification compliance by
/// following a few rules-of-thumb:
///
/// 1. All methods in the parser should be public.
///
/// 2. The methods follow the specified grammar, and do not implement custom
/// extensions, for example, `native`.
///
/// 3. The parser doesn't rewrite the token stream (when dealing with `>>`).
///
/// ### Implementing Extensions
///
/// For various reasons, some Dart language implementations have used
/// custom/unspecified extensions to the Dart grammar. Examples of this
/// includes diet parsing, patch files, `native` keyword, and generic
/// comments. This class isn't supposed to implement any of these
/// features. Instead it provides hooks for those extensions to be implemented
/// in subclasses or listeners. Let's examine how diet parsing and `native`
/// keyword is currently supported by Fasta.
///
/// #### Legacy Implementation of `native` Keyword
///
/// TODO(ahe,danrubel): Remove this section.
///
/// Both dart2js and the Dart VM have used the `native` keyword to mark methods
/// that couldn't be implemented in the Dart language and needed to be
/// implemented in JavaScript or C++, respectively. An example of the syntax
/// extension used by the Dart VM is:
///
/// nativeFunction() native "NativeFunction";
///
/// When attempting to parse this function, the parser eventually calls
/// [parseFunctionBody]. This method will report an unrecoverable error to the
/// listener with the code [fasta.messageExpectedFunctionBody]. The listener can
/// then look at the error code and the token and use the methods in
/// [native_support.dart](native_support.dart) to parse the native syntax.
///
/// #### Implementation of Diet Parsing
///
/// We call it _diet_ _parsing_ when the parser skips parts of a file. Both
/// dart2js and the Dart VM have been relying on this from early on as it allows
/// them to more quickly compile small programs that use small parts of big
/// libraries. It's also become an integrated part of how Fasta builds up
/// outlines before starting to parse method bodies.
///
/// When looking through this parser, you'll find a number of unused methods
/// starting with `skip`. These methods are only used by subclasses, such as
/// [ClassMemberParser](class_member_parser.dart) and
/// [TopLevelParser](top_level_parser.dart). These methods violate the
/// principle above about following the specified grammar, and originally lived
/// in subclasses. However, we realized that these methods were so widely used
/// and hard to maintain in subclasses, that it made sense to move them here.
///
/// ### Specification and Error Recovery
///
/// To improve error recovery, the parser will inform the listener of
/// recoverable errors and continue to parse. An example of a recoverable
/// error is:
///
/// Error: Asynchronous for-loop can only be used in 'async' or 'async*'...
/// main() { await for (var x in []) {} }
/// ^^^^^
///
/// ### Legacy Error Recovery
///
/// What's described below will be phased out in preference of the parser
/// reporting and recovering from syntax errors. The motivation for this is
/// that we have multiple listeners that use the parser, and this will ensure
/// consistency.
///
/// For unrecoverable errors, the parser will ask the listener for help to
/// recover from the error. We haven't made much progress on these kinds of
/// errors, so in most cases, the parser aborts by skipping to the end of file.
///
/// Historically, this parser has been rather lax in what it allows, and
/// deferred the enforcement of some syntactical rules to subsequent phases. It
/// doesn't matter how we got there, only that we've identified that it's
/// easier if the parser reports as many errors it can, but informs the
/// listener if the error is recoverable or not.
class Parser {
Listener listener;
Uri get uri => listener.uri;
bool mayParseFunctionExpressions = true;
/// Represents parser state: what asynchronous syntax is allowed in the
/// function being currently parsed. In rare situations, this can be set by
/// external clients, for example, to parse an expression outside a function.
AsyncModifier asyncState = AsyncModifier.Sync;
// TODO(danrubel): The [loopState] and associated functionality in the
// [Parser] duplicates work that the resolver needs to do when resolving
// break/continue targets. Long term, this state and functionality will be
// removed from the [Parser] class and the resolver will be responsible
// for generating all break/continue error messages.
/// Represents parser state: whether parsing outside a loop,
/// inside a loop, or inside a switch. This is used to determine whether
/// break and continue statements are allowed.
LoopState loopState = LoopState.OutsideLoop;
/// A rewriter for inserting synthetic tokens.
/// Access using [rewriter] for lazy initialization.
TokenStreamRewriter cachedRewriter;
TokenStreamRewriter get rewriter {
cachedRewriter ??= new TokenStreamRewriter();
return cachedRewriter;
}
Parser(this.listener);
bool get inGenerator {
return asyncState == AsyncModifier.AsyncStar ||
asyncState == AsyncModifier.SyncStar;
}
bool get inAsync {
return asyncState == AsyncModifier.Async ||
asyncState == AsyncModifier.AsyncStar;
}
bool get inPlainSync => asyncState == AsyncModifier.Sync;
bool get isBreakAllowed => loopState != LoopState.OutsideLoop;
bool get isContinueAllowed => loopState == LoopState.InsideLoop;
bool get isContinueWithLabelAllowed => loopState != LoopState.OutsideLoop;
/// Parse a compilation unit.
///
/// This method is only invoked from outside the parser. As a result, this
/// method takes the next token to be consumed rather than the last consumed
/// token and returns the token after the last consumed token rather than the
/// last consumed token.
///
/// ```
/// libraryDefinition:
/// scriptTag?
/// libraryName?
/// importOrExport*
/// partDirective*
/// topLevelDefinition*
/// ;
///
/// partDeclaration:
/// partHeader topLevelDefinition*
/// ;
/// ```
Token parseUnit(Token token) {
// Skip over error tokens and report them at the end
// so that the parser has the chance to adjust the error location.
Token errorToken = token;
token = skipErrorTokens(errorToken);
listener.beginCompilationUnit(token);
int count = 0;
DirectiveContext directiveState = new DirectiveContext();
token = syntheticPreviousToken(token);
if (identical(token.next.type, TokenType.SCRIPT_TAG)) {
directiveState?.checkScriptTag(this, token.next);
token = parseScript(token);
}
while (!token.next.isEof) {
final Token start = token.next;
token = parseTopLevelDeclarationImpl(token, directiveState);
listener.endTopLevelDeclaration(token.next);
count++;
if (start == token.next) {
// Recovery:
// If progress has not been made reaching the end of the token stream,
// then report an error and skip the current token.
token = token.next;
listener.beginMetadataStar(token);
listener.endMetadataStar(0);
reportRecoverableErrorWithToken(
token, fasta.templateExpectedDeclaration);
listener.handleInvalidTopLevelDeclaration(token);
listener.endTopLevelDeclaration(token.next);
count++;
}
}
token = token.next;
reportAllErrorTokens(errorToken);
listener.endCompilationUnit(count, token);
// Clear fields that could lead to memory leak.
cachedRewriter = null;
return token;
}
/// This method exists for analyzer compatibility only
/// and will be removed once analyzer/fasta integration is complete.
///
/// Similar to [parseUnit], this method parses a compilation unit,
/// but stops when it reaches the first declaration or EOF.
///
/// This method is only invoked from outside the parser. As a result, this
/// method takes the next token to be consumed rather than the last consumed
/// token and returns the token after the last consumed token rather than the
/// last consumed token.
Token parseDirectives(Token token) {
listener.beginCompilationUnit(token);
int count = 0;
DirectiveContext directiveState = new DirectiveContext();
token = syntheticPreviousToken(token);
while (!token.next.isEof) {
final Token start = token.next;
final String nextValue = start.next.stringValue;
// If a built-in keyword is being used as function name, then stop.
if (identical(nextValue, '.') ||
identical(nextValue, '<') ||
identical(nextValue, '(')) {
break;
}
if (identical(token.next.type, TokenType.SCRIPT_TAG)) {
directiveState?.checkScriptTag(this, token.next);
token = parseScript(token);
} else {
token = parseMetadataStar(token);
Token keyword = token.next;
final String value = keyword.stringValue;
if (identical(value, 'import')) {
directiveState?.checkImport(this, keyword);
token = parseImport(keyword);
} else if (identical(value, 'export')) {
directiveState?.checkExport(this, keyword);
token = parseExport(keyword);
} else if (identical(value, 'library')) {
directiveState?.checkLibrary(this, keyword);
token = parseLibraryName(keyword);
} else if (identical(value, 'part')) {
token = parsePartOrPartOf(keyword, directiveState);
} else if (identical(value, ';')) {
token = start;
} else {
listener.handleDirectivesOnly();
break;
}
}
listener.endTopLevelDeclaration(token.next);
}
token = token.next;
listener.endCompilationUnit(count, token);
// Clear fields that could lead to memory leak.
cachedRewriter = null;
return token;
}
/// Parse a top-level declaration.
///
/// This method is only invoked from outside the parser. As a result, this
/// method takes the next token to be consumed rather than the last consumed
/// token and returns the token after the last consumed token rather than the
/// last consumed token.
Token parseTopLevelDeclaration(Token token) {
token =
parseTopLevelDeclarationImpl(syntheticPreviousToken(token), null).next;
listener.endTopLevelDeclaration(token);
return token;
}
/// ```
/// topLevelDefinition:
/// classDefinition |
/// enumType |
/// typeAlias |
/// 'external'? functionSignature ';' |
/// 'external'? getterSignature ';' |
/// 'external''? setterSignature ';' |
/// functionSignature functionBody |
/// returnType? 'get' identifier functionBody |
/// returnType? 'set' identifier formalParameterList functionBody |
/// ('final' | 'const') type? staticFinalDeclarationList ';' |
/// variableDeclaration ';'
/// ;
/// ```
Token parseTopLevelDeclarationImpl(
Token token, DirectiveContext directiveState) {
token = parseMetadataStar(token);
Token next = token.next;
if (next.isTopLevelKeyword) {
return parseTopLevelKeywordDeclaration(token, next, directiveState);
}
Token start = token;
// Skip modifiers to find a top level keyword or identifier
if (next.isModifier) {
if (optional('var', next) ||
optional('late', next) ||
((optional('const', next) || optional('final', next)) &&
// Ignore `const class` and `final class` so that it is reported
// below as an invalid modifier on a class.
!optional('class', next.next))) {
directiveState?.checkDeclaration();
return parseTopLevelMemberImpl(token);
}
while (token.next.isModifier) {
token = token.next;
}
}
next = token.next;
if (next.isTopLevelKeyword) {
return parseTopLevelKeywordDeclaration(start, next, directiveState);
} else if (next.isKeywordOrIdentifier) {
// TODO(danrubel): improve parseTopLevelMember
// so that we don't parse modifiers twice.
directiveState?.checkDeclaration();
return parseTopLevelMemberImpl(start);
} else if (start.next != next) {
directiveState?.checkDeclaration();
// Handle the edge case where a modifier is being used as an identifier
return parseTopLevelMemberImpl(start);
}
// Recovery
if (next.isOperator && optional('(', next.next)) {
// This appears to be a top level operator declaration, which is invalid.
reportRecoverableError(next, fasta.messageTopLevelOperator);
// Insert a synthetic identifier
// and continue parsing as a top level function.
rewriter.insertSyntheticIdentifier(
next, '#synthetic_function_${next.charOffset}');
return parseTopLevelMemberImpl(next);
}
// Ignore any preceding modifiers and just report the unexpected token
listener.beginTopLevelMember(next);
return parseInvalidTopLevelDeclaration(token);
}
/// Parse the modifiers before the `class` keyword.
/// Return the first `abstract` modifier or `null` if not found.
Token parseClassDeclarationModifiers(Token start, Token keyword) {
Token modifier = start.next;
while (modifier != keyword) {
if (optional('abstract', modifier)) {
parseTopLevelKeywordModifiers(modifier, keyword);
return modifier;
} else {
// Recovery
reportTopLevelModifierError(modifier, keyword);
}
modifier = modifier.next;
}
return null;
}
/// Report errors on any modifiers before the specified keyword.
void parseTopLevelKeywordModifiers(Token start, Token keyword) {
Token modifier = start.next;
while (modifier != keyword) {
// Recovery
reportTopLevelModifierError(modifier, keyword);
modifier = modifier.next;
}
}
// Report an error for the given modifier preceding a top level keyword
// such as `import` or `class`.
void reportTopLevelModifierError(Token modifier, Token afterModifiers) {
if (optional('const', modifier) && optional('class', afterModifiers)) {
reportRecoverableError(modifier, fasta.messageConstClass);
} else if (optional('external', modifier)) {
if (optional('class', afterModifiers)) {
reportRecoverableError(modifier, fasta.messageExternalClass);
} else if (optional('enum', afterModifiers)) {
reportRecoverableError(modifier, fasta.messageExternalEnum);
} else if (optional('typedef', afterModifiers)) {
reportRecoverableError(modifier, fasta.messageExternalTypedef);
} else {
reportRecoverableErrorWithToken(
modifier, fasta.templateExtraneousModifier);
}
} else {
reportRecoverableErrorWithToken(
modifier, fasta.templateExtraneousModifier);
}
}
/// Parse any top-level declaration that begins with a keyword.
/// [start] is the token before any modifiers preceding [keyword].
Token parseTopLevelKeywordDeclaration(
Token start, Token keyword, DirectiveContext directiveState) {
assert(keyword.isTopLevelKeyword);
final String value = keyword.stringValue;
if (identical(value, 'class')) {
directiveState?.checkDeclaration();
Token abstractToken = parseClassDeclarationModifiers(start, keyword);
return parseClassOrNamedMixinApplication(abstractToken, keyword);
} else if (identical(value, 'enum')) {
directiveState?.checkDeclaration();
parseTopLevelKeywordModifiers(start, keyword);
return parseEnum(keyword);
} else {
// The remaining top level keywords are built-in keywords
// and can be used in a top level declaration
// as an identifier such as "abstract<T>() => 0;"
// or as a prefix such as "abstract.A b() => 0;".
String nextValue = keyword.next.stringValue;
if (identical(nextValue, '(') || identical(nextValue, '.')) {
directiveState?.checkDeclaration();
return parseTopLevelMemberImpl(start);
} else if (identical(nextValue, '<')) {
if (identical(value, 'extension')) {
// The name in an extension declaration is optional:
// `extension<T> on ...`
Token endGroup = keyword.next.endGroup;
if (endGroup != null && optional('on', endGroup.next)) {
directiveState?.checkDeclaration();
return parseExtension(keyword);
}
}
directiveState?.checkDeclaration();
return parseTopLevelMemberImpl(start);
} else {
parseTopLevelKeywordModifiers(start, keyword);
if (identical(value, 'import')) {
directiveState?.checkImport(this, keyword);
return parseImport(keyword);
} else if (identical(value, 'export')) {
directiveState?.checkExport(this, keyword);
return parseExport(keyword);
} else if (identical(value, 'typedef')) {
directiveState?.checkDeclaration();
return parseTypedef(keyword);
} else if (identical(value, 'mixin')) {
directiveState?.checkDeclaration();
return parseMixin(keyword);
} else if (identical(value, 'extension')) {
directiveState?.checkDeclaration();
return parseExtension(keyword);
} else if (identical(value, 'part')) {
return parsePartOrPartOf(keyword, directiveState);
} else if (identical(value, 'library')) {
directiveState?.checkLibrary(this, keyword);
return parseLibraryName(keyword);
}
}
}
throw "Internal error: Unhandled top level keyword '$value'.";
}
/// ```
/// libraryDirective:
/// 'library' qualified ';'
/// ;
/// ```
Token parseLibraryName(Token libraryKeyword) {
assert(optional('library', libraryKeyword));
listener.beginLibraryName(libraryKeyword);
Token token = parseQualified(libraryKeyword, IdentifierContext.libraryName,
IdentifierContext.libraryNameContinuation);
token = ensureSemicolon(token);
listener.endLibraryName(libraryKeyword, token);
return token;
}
/// ```
/// importPrefix:
/// 'deferred'? 'as' identifier
/// ;
/// ```
Token parseImportPrefixOpt(Token token) {
Token next = token.next;
if (optional('deferred', next) && optional('as', next.next)) {
Token deferredToken = next;
Token asKeyword = next.next;
token = ensureIdentifier(
asKeyword, IdentifierContext.importPrefixDeclaration);
listener.handleImportPrefix(deferredToken, asKeyword);
} else if (optional('as', next)) {
Token asKeyword = next;
token = ensureIdentifier(next, IdentifierContext.importPrefixDeclaration);
listener.handleImportPrefix(null, asKeyword);
} else {
listener.handleImportPrefix(null, null);
}
return token;
}
/// ```
/// importDirective:
/// 'import' uri ('if' '(' test ')' uri)* importPrefix? combinator* ';'
/// ;
/// ```
Token parseImport(Token importKeyword) {
assert(optional('import', importKeyword));
listener.beginImport(importKeyword);
Token token = ensureLiteralString(importKeyword);
Token uri = token;
token = parseConditionalUriStar(token);
token = parseImportPrefixOpt(token);
token = parseCombinatorStar(token).next;
if (optional(';', token)) {
listener.endImport(importKeyword, token);
return token;
} else {
// Recovery
listener.endImport(importKeyword, null);
return parseImportRecovery(uri);
}
}
/// Recover given out-of-order clauses in an import directive where [token] is
/// the import keyword.
Token parseImportRecovery(Token token) {
final Listener primaryListener = listener;
final ImportRecoveryListener recoveryListener =
new ImportRecoveryListener();
// Reparse to determine which clauses have already been parsed
// but intercept the events so they are not sent to the primary listener
listener = recoveryListener;
token = parseConditionalUriStar(token);
token = parseImportPrefixOpt(token);
token = parseCombinatorStar(token);
Token firstDeferredKeyword = recoveryListener.deferredKeyword;
bool hasPrefix = recoveryListener.asKeyword != null;
bool hasCombinator = recoveryListener.hasCombinator;
// Update the recovery listener to forward subsequent events
// to the primary listener
recoveryListener.listener = primaryListener;
// Parse additional out-of-order clauses.
Token semicolon;
do {
Token start = token.next;
// Check for extraneous token in the middle of an import statement.
token = skipUnexpectedTokenOpt(
token, const <String>['if', 'deferred', 'as', 'hide', 'show', ';']);
// During recovery, clauses are parsed in the same order
// and generate the same events as in the parseImport method above.
recoveryListener.clear();
token = parseConditionalUriStar(token);
if (recoveryListener.ifKeyword != null) {
if (firstDeferredKeyword != null) {
// TODO(danrubel): report error indicating conditional should
// be moved before deferred keyword
} else if (hasPrefix) {
// TODO(danrubel): report error indicating conditional should
// be moved before prefix clause
} else if (hasCombinator) {
// TODO(danrubel): report error indicating conditional should
// be moved before combinators
}
}
if (optional('deferred', token.next) &&
!optional('as', token.next.next)) {
listener.handleImportPrefix(token.next, null);
token = token.next;
} else {
token = parseImportPrefixOpt(token);
}
if (recoveryListener.deferredKeyword != null) {
if (firstDeferredKeyword != null) {
reportRecoverableError(
recoveryListener.deferredKeyword, fasta.messageDuplicateDeferred);
} else {
if (hasPrefix) {
reportRecoverableError(recoveryListener.deferredKeyword,
fasta.messageDeferredAfterPrefix);
}
firstDeferredKeyword = recoveryListener.deferredKeyword;
}
}
if (recoveryListener.asKeyword != null) {
if (hasPrefix) {
reportRecoverableError(
recoveryListener.asKeyword, fasta.messageDuplicatePrefix);
} else {
if (hasCombinator) {
reportRecoverableError(
recoveryListener.asKeyword, fasta.messagePrefixAfterCombinator);
}
hasPrefix = true;
}
}
token = parseCombinatorStar(token);
hasCombinator = hasCombinator || recoveryListener.hasCombinator;
if (optional(';', token.next)) {
semicolon = token.next;
} else if (identical(start, token.next)) {
// If no forward progress was made, insert ';' so that we exit loop.
semicolon = ensureSemicolon(token);
}
listener.handleRecoverImport(semicolon);
} while (semicolon == null);
if (firstDeferredKeyword != null && !hasPrefix) {
reportRecoverableError(
firstDeferredKeyword, fasta.messageMissingPrefixInDeferredImport);
}
return semicolon;
}
/// ```
/// conditionalUris:
/// conditionalUri*
/// ;
/// ```
Token parseConditionalUriStar(Token token) {
listener.beginConditionalUris(token.next);
int count = 0;
while (optional('if', token.next)) {
count++;
token = parseConditionalUri(token);
}
listener.endConditionalUris(count);
return token;
}
/// ```
/// conditionalUri:
/// 'if' '(' dottedName ('==' literalString)? ')' uri
/// ;
/// ```
Token parseConditionalUri(Token token) {
Token ifKeyword = token = token.next;
assert(optional('if', token));
listener.beginConditionalUri(token);
Token leftParen = token.next;
if (!optional('(', leftParen)) {
reportRecoverableError(
leftParen, fasta.templateExpectedButGot.withArguments('('));
leftParen = rewriter.insertParens(token, true);
}
token = parseDottedName(leftParen);
Token next = token.next;
Token equalitySign;
if (optional('==', next)) {
equalitySign = next;
token = ensureLiteralString(next);
next = token.next;
}
if (next != leftParen.endGroup) {
Token endGroup = leftParen.endGroup;
if (endGroup.isSynthetic) {
// The scanner did not place the synthetic ')' correctly, so move it.
next = rewriter.moveSynthetic(token, endGroup);
} else {
reportRecoverableErrorWithToken(next, fasta.templateUnexpectedToken);
next = endGroup;
}
}
token = next;
assert(optional(')', token));
token = ensureLiteralString(token);
listener.endConditionalUri(ifKeyword, leftParen, equalitySign);
return token;
}
/// ```
/// dottedName:
/// identifier ('.' identifier)*
/// ;
/// ```
Token parseDottedName(Token token) {
token = ensureIdentifier(token, IdentifierContext.dottedName);
Token firstIdentifier = token;
int count = 1;
while (optional('.', token.next)) {
token = ensureIdentifier(
token.next, IdentifierContext.dottedNameContinuation);
count++;
}
listener.handleDottedName(count, firstIdentifier);
return token;
}
/// ```
/// exportDirective:
/// 'export' uri conditional-uris* combinator* ';'
/// ;
/// ```
Token parseExport(Token exportKeyword) {
assert(optional('export', exportKeyword));
listener.beginExport(exportKeyword);
Token token = ensureLiteralString(exportKeyword);
token = parseConditionalUriStar(token);
token = parseCombinatorStar(token);
token = ensureSemicolon(token);
listener.endExport(exportKeyword, token);
return token;
}
/// ```
/// combinators:
/// (hideCombinator | showCombinator)*
/// ;
/// ```
Token parseCombinatorStar(Token token) {
Token next = token.next;
listener.beginCombinators(next);
int count = 0;
while (true) {
String value = next.stringValue;
if (identical('hide', value)) {
token = parseHide(token);
} else if (identical('show', value)) {
token = parseShow(token);
} else {
listener.endCombinators(count);
break;
}
next = token.next;
count++;
}
return token;
}
/// ```
/// hideCombinator:
/// 'hide' identifierList
/// ;
/// ```
Token parseHide(Token token) {
Token hideKeyword = token.next;
assert(optional('hide', hideKeyword));
listener.beginHide(hideKeyword);
token = parseIdentifierList(hideKeyword);
listener.endHide(hideKeyword);
return token;
}
/// ```
/// showCombinator:
/// 'show' identifierList
/// ;
/// ```
Token parseShow(Token token) {
Token showKeyword = token.next;
assert(optional('show', showKeyword));
listener.beginShow(showKeyword);
token = parseIdentifierList(showKeyword);
listener.endShow(showKeyword);
return token;
}
/// ```
/// identifierList:
/// identifier (',' identifier)*
/// ;
/// ```
Token parseIdentifierList(Token token) {
token = ensureIdentifier(token, IdentifierContext.combinator);
int count = 1;
while (optional(',', token.next)) {
token = ensureIdentifier(token.next, IdentifierContext.combinator);
count++;
}
listener.handleIdentifierList(count);
return token;
}
/// ```
/// typeList:
/// type (',' type)*
/// ;
/// ```
Token parseTypeList(Token token) {
listener.beginTypeList(token.next);
token = computeType(token, true).ensureTypeOrVoid(token, this);
int count = 1;
while (optional(',', token.next)) {
token = computeType(token.next, true).ensureTypeOrVoid(token.next, this);
count++;
}
listener.endTypeList(count);
return token;
}
Token parsePartOrPartOf(Token partKeyword, DirectiveContext directiveState) {
assert(optional('part', partKeyword));
if (optional('of', partKeyword.next)) {
directiveState?.checkPartOf(this, partKeyword);
return parsePartOf(partKeyword);
} else {
directiveState?.checkPart(this, partKeyword);
return parsePart(partKeyword);
}
}
/// ```
/// partDirective:
/// 'part' uri ';'
/// ;
/// ```
Token parsePart(Token partKeyword) {
assert(optional('part', partKeyword));
listener.beginPart(partKeyword);
Token token = ensureLiteralString(partKeyword);
token = ensureSemicolon(token);
listener.endPart(partKeyword, token);
return token;
}
/// ```
/// partOfDirective:
/// 'part' 'of' (qualified | uri) ';'
/// ;
/// ```
Token parsePartOf(Token partKeyword) {
Token ofKeyword = partKeyword.next;
assert(optional('part', partKeyword));
assert(optional('of', ofKeyword));
listener.beginPartOf(partKeyword);
bool hasName = ofKeyword.next.isIdentifier;
Token token;
if (hasName) {
token = parseQualified(ofKeyword, IdentifierContext.partName,
IdentifierContext.partNameContinuation);
} else {
token = ensureLiteralString(ofKeyword);
}
token = ensureSemicolon(token);
listener.endPartOf(partKeyword, ofKeyword, token, hasName);
return token;
}
/// ```
/// metadata:
/// annotation*
/// ;
/// ```
Token parseMetadataStar(Token token) {
listener.beginMetadataStar(token.next);
int count = 0;
while (optional('@', token.next)) {
token = parseMetadata(token);
count++;
}
listener.endMetadataStar(count);
return token;
}
/// ```
/// annotation:
/// '@' qualified ('.' identifier)? arguments?
/// ;
/// ```
Token parseMetadata(Token token) {
Token atToken = token.next;
assert(optional('@', atToken));
listener.beginMetadata(atToken);
token = ensureIdentifier(atToken, IdentifierContext.metadataReference);
token =
parseQualifiedRestOpt(token, IdentifierContext.metadataContinuation);
if (optional("<", token.next)) {
reportRecoverableError(token.next, fasta.messageMetadataTypeArguments);
}
token = computeTypeParamOrArg(token).parseArguments(token, this);
Token period = null;
if (optional('.', token.next)) {
period = token.next;
token = ensureIdentifier(
period, IdentifierContext.metadataContinuationAfterTypeArguments);
}
token = parseArgumentsOpt(token);
listener.endMetadata(atToken, period, token.next);
return token;
}
/// ```
/// scriptTag:
/// '#!' (˜NEWLINE)* NEWLINE
/// ;
/// ```
Token parseScript(Token token) {
token = token.next;
assert(identical(token.type, TokenType.SCRIPT_TAG));
listener.handleScript(token);
return token;
}
/// ```
/// typeAlias:
/// metadata 'typedef' typeAliasBody |
/// metadata 'typedef' identifier typeParameters? '=' functionType ';'
/// ;
///
/// functionType:
/// returnType? 'Function' typeParameters? parameterTypeList
///
/// typeAliasBody:
/// functionTypeAlias
/// ;
///
/// functionTypeAlias:
/// functionPrefix typeParameters? formalParameterList ;
/// ;
///
/// functionPrefix:
/// returnType? identifier
/// ;
/// ```
Token parseTypedef(Token typedefKeyword) {
assert(optional('typedef', typedefKeyword));
listener.beginFunctionTypeAlias(typedefKeyword);
TypeInfo typeInfo = computeType(typedefKeyword, false);
Token token = typeInfo.skipType(typedefKeyword).next;
Token equals;
TypeParamOrArgInfo typeParam = computeTypeParamOrArg(token, true);
if (typeInfo == noType &&
(token.kind == IDENTIFIER_TOKEN || token.type.isPseudo) &&
optional('=', typeParam.skip(token).next)) {
listener.handleIdentifier(token, IdentifierContext.typedefDeclaration);
token = typeParam.parseVariables(token, this).next;
// parseVariables rewrites so even though we checked in the if,
// we might not have an equal here now.
if (!optional('=', token) && optional('=', token.next)) {
// Recovery after recovery: A token was inserted, but we'll skip it now
// to get more in line with what we thought in the if before.
token = token.next;
}
if (optional('=', token)) {
equals = token;
token = computeType(equals, true).ensureTypeOrVoid(equals, this);
} else {
// A rewrite caused the = to disappear
token = parseFormalParametersRequiredOpt(
token, MemberKind.FunctionTypeAlias);
}
} else {
token = typeInfo.parseType(typedefKeyword, this);
token = ensureIdentifier(token, IdentifierContext.typedefDeclaration);
token = typeParam.parseVariables(token, this);
token =
parseFormalParametersRequiredOpt(token, MemberKind.FunctionTypeAlias);
}
token = ensureSemicolon(token);
listener.endFunctionTypeAlias(typedefKeyword, equals, token);
return token;
}
/// Parse a mixin application starting from `with`. Assumes that the first
/// type has already been parsed.
Token parseMixinApplicationRest(Token token) {
Token withKeyword = token.next;
if (!optional('with', withKeyword)) {
// Recovery: Report an error and insert synthetic `with` clause.
reportRecoverableError(
withKeyword, fasta.templateExpectedButGot.withArguments('with'));
withKeyword = rewriter.insertSyntheticKeyword(token, Keyword.WITH);
if (!isValidTypeReference(withKeyword.next)) {
rewriter.insertSyntheticIdentifier(withKeyword);
}
}
token = parseTypeList(withKeyword);
listener.handleNamedMixinApplicationWithClause(withKeyword);
return token;
}
Token parseWithClauseOpt(Token token) {
Token withKeyword = token.next;
if (optional('with', withKeyword)) {
token = parseTypeList(withKeyword);
listener.handleClassWithClause(withKeyword);
} else {
listener.handleClassNoWithClause();
}
return token;
}
/// Parse the formal parameters of a getter (which shouldn't have parameters)
/// or function or method.
Token parseGetterOrFormalParameters(
Token token, Token name, bool isGetter, MemberKind kind) {
Token next = token.next;
if (optional("(", next)) {
if (isGetter) {
reportRecoverableError(next, fasta.messageGetterWithFormals);
}
token = parseFormalParameters(token, kind);
} else if (isGetter) {
listener.handleNoFormalParameters(next, kind);
} else {
// Recovery
if (optional('operator', name)) {
Token next = name.next;
if (next.isOperator) {
name = next;
} else if (isUnaryMinus(next)) {
name = next.next;
}
}
reportRecoverableError(name, missingParameterMessage(kind));
token = rewriter.insertParens(token, false);
token = parseFormalParametersRest(token, kind);
}
return token;
}
Token parseFormalParametersOpt(Token token, MemberKind kind) {
Token next = token.next;
if (optional('(', next)) {
token = parseFormalParameters(token, kind);
} else {
listener.handleNoFormalParameters(next, kind);
}
return token;
}
Token skipFormalParameters(Token token, MemberKind kind) {
return skipFormalParametersRest(token.next, kind);
}
Token skipFormalParametersRest(Token token, MemberKind kind) {
assert(optional('(', token));
// TODO(ahe): Shouldn't this be `beginFormalParameters`?
listener.beginOptionalFormalParameters(token);
Token closeBrace = token.endGroup;
assert(optional(')', closeBrace));
listener.endFormalParameters(0, token, closeBrace, kind);
return closeBrace;
}
/// Parses the formal parameter list of a function.
///
/// If `kind == MemberKind.GeneralizedFunctionType`, then names may be
/// omitted (except for named arguments). Otherwise, types may be omitted.
Token parseFormalParametersRequiredOpt(Token token, MemberKind kind) {
Token next = token.next;
if (!optional('(', next)) {
reportRecoverableError(next, missingParameterMessage(kind));
next = rewriter.insertParens(token, false);
}
return parseFormalParametersRest(next, kind);
}
/// Parses the formal parameter list of a function given that the left
/// parenthesis is known to exist.
///
/// If `kind == MemberKind.GeneralizedFunctionType`, then names may be
/// omitted (except for named arguments). Otherwise, types may be omitted.
Token parseFormalParameters(Token token, MemberKind kind) {
return parseFormalParametersRest(token.next, kind);
}
/// Parses the formal parameter list of a function given that the left
/// parenthesis passed in as [token].
///
/// If `kind == MemberKind.GeneralizedFunctionType`, then names may be
/// omitted (except for named arguments). Otherwise, types may be omitted.
Token parseFormalParametersRest(Token token, MemberKind kind) {
Token begin = token;
assert(optional('(', token));
listener.beginFormalParameters(begin, kind);
int parameterCount = 0;
while (true) {
Token next = token.next;
if (optional(')', next)) {
token = next;
break;
}
++parameterCount;
String value = next.stringValue;
if (identical(value, '[')) {
token = parseOptionalPositionalParameters(token, kind);
token = ensureCloseParen(token, begin);
break;
} else if (identical(value, '{')) {
token = parseOptionalNamedParameters(token, kind);
token = ensureCloseParen(token, begin);
break;
} else if (identical(value, '[]')) {
// Recovery
token = rewriteSquareBrackets(token);
token = parseOptionalPositionalParameters(token, kind);
token = ensureCloseParen(token, begin);
break;
}
token = parseFormalParameter(token, FormalParameterKind.mandatory, kind);
next = token.next;
if (!optional(',', next)) {
Token next = token.next;
if (optional(')', next)) {
token = next;
} else {
// Recovery
if (begin.endGroup.isSynthetic) {
// Scanner has already reported a missing `)` error,
// but placed the `)` in the wrong location, so move it.
token = rewriter.moveSynthetic(token, begin.endGroup);
} else if (next.kind == IDENTIFIER_TOKEN &&
next.next.kind == IDENTIFIER_TOKEN) {
// Looks like a missing comma
token = rewriteAndRecover(
token,
fasta.templateExpectedButGot.withArguments(','),
new SyntheticToken(TokenType.COMMA, next.charOffset));
continue;
} else {
token = ensureCloseParen(token, begin);
}
}
break;
}
token = next;
}
assert(optional(')', token));
listener.endFormalParameters(parameterCount, begin, token, kind);
return token;
}
/// Return the message that should be produced when the formal parameters are
/// missing.
Message missingParameterMessage(MemberKind kind) {
if (kind == MemberKind.FunctionTypeAlias) {
return fasta.messageMissingTypedefParameters;
} else if (kind == MemberKind.NonStaticMethod ||
kind == MemberKind.StaticMethod) {
return fasta.messageMissingMethodParameters;
}
return fasta.messageMissingFunctionParameters;
}
/// ```
/// normalFormalParameter:
/// functionFormalParameter |
/// fieldFormalParameter |
/// simpleFormalParameter
/// ;
///
/// functionFormalParameter:
/// metadata 'covariant'? returnType? identifier formalParameterList
/// ;
///
/// simpleFormalParameter:
/// metadata 'covariant'? finalConstVarOrType? identifier |
/// ;
///
/// fieldFormalParameter:
/// metadata finalConstVarOrType? 'this' '.' identifier formalParameterList?
/// ;
/// ```
Token parseFormalParameter(
Token token, FormalParameterKind parameterKind, MemberKind memberKind) {
assert(parameterKind != null);
token = parseMetadataStar(token);
Token next = token.next;
Token start = next;
final bool inFunctionType =
memberKind == MemberKind.GeneralizedFunctionType;
Token requiredToken;
Token covariantToken;
Token varFinalOrConst;
if (isModifier(next)) {
if (optional('required', next)) {
if (parameterKind == FormalParameterKind.optionalNamed) {
requiredToken = token = next;
next = token.next;
}
}
if (isModifier(next)) {
if (optional('covariant', next)) {
if (memberKind != MemberKind.StaticMethod &&
memberKind != MemberKind.TopLevelMethod &&
memberKind != MemberKind.ExtensionNonStaticMethod &&
memberKind != MemberKind.ExtensionStaticMethod) {
covariantToken = token = next;
next = token.next;
}
}
if (isModifier(next)) {
if (!inFunctionType) {
if (optional('var', next)) {
varFinalOrConst = token = next;
next = token.next;
} else if (optional('final', next)) {
varFinalOrConst = token = next;
next = token.next;
}
}
if (isModifier(next)) {
// Recovery
ModifierRecoveryContext context = new ModifierRecoveryContext(this)
..covariantToken = covariantToken
..requiredToken = requiredToken
..varFinalOrConst = varFinalOrConst;
token = context.parseFormalParameterModifiers(
token, parameterKind, memberKind);
next = token.next;
covariantToken = context.covariantToken;
requiredToken = context.requiredToken;
varFinalOrConst = context.varFinalOrConst;
context = null;
}
}
}
}
listener.beginFormalParameter(
start, memberKind, requiredToken, covariantToken, varFinalOrConst);
// Type is required in a generalized function type, but optional otherwise.
final Token beforeType = token;
TypeInfo typeInfo = computeType(token, inFunctionType);
token = typeInfo.skipType(token);
next = token.next;
if (typeInfo == noType &&
(optional('.', next) ||
(next.isIdentifier && optional('.', next.next)))) {
// Recovery: Malformed type reference.
typeInfo = computeType(beforeType, true);
token = typeInfo.skipType(beforeType);
next = token.next;
}
final bool isNamedParameter =
parameterKind == FormalParameterKind.optionalNamed;
Token thisKeyword;
Token periodAfterThis;
IdentifierContext nameContext =
IdentifierContext.formalParameterDeclaration;
if (!inFunctionType && optional('this', next)) {
thisKeyword = token = next;
next = token.next;
if (!optional('.', next)) {
// Recover from a missing period by inserting one.
next = rewriteAndRecover(
token,
fasta.templateExpectedButGot.withArguments('.'),
new SyntheticToken(TokenType.PERIOD, next.charOffset));
}
periodAfterThis = token = next;
next = token.next;
nameContext = IdentifierContext.fieldInitializer;
}
if (next.isIdentifier) {
token = next;
next = token.next;
}
Token beforeInlineFunctionType;
TypeParamOrArgInfo typeParam = noTypeParamOrArg;
if (optional("<", next)) {
typeParam = computeTypeParamOrArg(token);
if (typeParam != noTypeParamOrArg) {
Token closer = typeParam.skip(token);
if (optional("(", closer.next)) {
if (varFinalOrConst != null) {
reportRecoverableError(
varFinalOrConst, fasta.messageFunctionTypedParameterVar);
}
beforeInlineFunctionType = token;
token = closer.next.endGroup;
next = token.next;
}
}
} else if (optional("(", next)) {
if (varFinalOrConst != null) {
reportRecoverableError(
varFinalOrConst, fasta.messageFunctionTypedParameterVar);
}
beforeInlineFunctionType = token;
token = next.endGroup;
next = token.next;
}
if (typeInfo != noType &&
varFinalOrConst != null &&
optional('var', varFinalOrConst)) {
reportRecoverableError(varFinalOrConst, fasta.messageTypeAfterVar);
}
Token endInlineFunctionType;
if (beforeInlineFunctionType != null) {
endInlineFunctionType =
typeParam.parseVariables(beforeInlineFunctionType, this);
listener.beginFunctionTypedFormalParameter(beforeInlineFunctionType.next);
token = typeInfo.parseType(beforeType, this);
endInlineFunctionType = parseFormalParametersRequiredOpt(
endInlineFunctionType, MemberKind.FunctionTypedParameter);
Token question;
if (optional('?', endInlineFunctionType.next)) {
question = endInlineFunctionType = endInlineFunctionType.next;
}
listener.endFunctionTypedFormalParameter(
beforeInlineFunctionType, question);
// Generalized function types don't allow inline function types.
// The following isn't allowed:
// int Function(int bar(String x)).
if (inFunctionType) {
reportRecoverableError(beforeInlineFunctionType.next,
fasta.messageInvalidInlineFunctionType);
}
} else if (inFunctionType) {
token = typeInfo.ensureTypeOrVoid(beforeType, this);
} else {
token = typeInfo.parseType(beforeType, this);
}
Token nameToken;
if (periodAfterThis != null) {
token = periodAfterThis;
}
next = token.next;
if (inFunctionType &&
!isNamedParameter &&
!next.isKeywordOrIdentifier &&
beforeInlineFunctionType == null) {
nameToken = token.next;
listener.handleNoName(nameToken);
} else {
nameToken = token = ensureIdentifier(token, nameContext);
if (isNamedParameter && nameToken.lexeme.startsWith("_")) {
reportRecoverableError(nameToken, fasta.messagePrivateNamedParameter);
}
}
if (endInlineFunctionType != null) {
token = endInlineFunctionType;
}
next = token.next;
String value = next.stringValue;
Token initializerStart, initializerEnd;
if ((identical('=', value)) || (identical(':', value))) {
Token equal = next;
initializerStart = equal.next;
listener.beginFormalParameterDefaultValueExpression();
token = initializerEnd = parseExpression(equal);
next = token.next;
listener.endFormalParameterDefaultValueExpression();
// TODO(danrubel): Consider removing the last parameter from the
// handleValuedFormalParameter event... it appears to be unused.
listener.handleValuedFormalParameter(equal, next);
if (isMandatoryFormalParameterKind(parameterKind)) {
reportRecoverableError(
equal, fasta.messageRequiredParameterWithDefault);
} else if (isOptionalPositionalFormalParameterKind(parameterKind) &&
identical(':', value)) {
reportRecoverableError(
equal, fasta.messagePositionalParameterWithEquals);
} else if (inFunctionType ||
memberKind == MemberKind.FunctionTypeAlias ||
memberKind == MemberKind.FunctionTypedParameter) {
reportRecoverableError(equal, fasta.messageFunctionTypeDefaultValue);
}
} else {
listener.handleFormalParameterWithoutValue(next);
}
listener.endFormalParameter(thisKeyword, periodAfterThis, nameToken,
initializerStart, initializerEnd, parameterKind, memberKind);
return token;
}
/// ```
/// defaultFormalParameter:
/// normalFormalParameter ('=' expression)?
/// ;
/// ```
Token parseOptionalPositionalParameters(Token token, MemberKind kind) {
Token begin = token = token.next;
assert(optional('[', token));
listener.beginOptionalFormalParameters(begin);
int parameterCount = 0;
while (true) {
Token next = token.next;
if (optional(']', next)) {
break;
}
token = parseFormalParameter(
token, FormalParameterKind.optionalPositional, kind);
next = token.next;
++parameterCount;
if (!optional(',', next)) {
if (!optional(']', next)) {
// Recovery
reportRecoverableError(
next, fasta.templateExpectedButGot.withArguments(']'));
// Scanner guarantees a closing bracket.
next = begin.endGroup;
while (token.next != next) {
token = token.next;
}
}
break;
}
token = next;
}
if (parameterCount == 0) {
rewriteAndRecover(
token,
fasta.messageEmptyOptionalParameterList,
new SyntheticStringToken(
TokenType.IDENTIFIER, '', token.next.charOffset, 0));
token = parseFormalParameter(
token, FormalParameterKind.optionalPositional, kind);
++parameterCount;
}
token = token.next;
assert(optional(']', token));
listener.endOptionalFormalParameters(parameterCount, begin, token);
return token;
}
/// ```
/// defaultNamedParameter:
/// normalFormalParameter ('=' expression)? |
/// normalFormalParameter (':' expression)?
/// ;
/// ```
Token parseOptionalNamedParameters(Token token, MemberKind kind) {
Token begin = token = token.next;
assert(optional('{', token));
listener.beginOptionalFormalParameters(begin);
int parameterCount = 0;
while (true) {
Token next = token.next;
if (optional('}', next)) {
break;
}
token =
parseFormalParameter(token, FormalParameterKind.optionalNamed, kind);
next = token.next;
++parameterCount;
if (!optional(',', next)) {
if (!optional('}', next)) {
// Recovery
reportRecoverableError(
next, fasta.templateExpectedButGot.withArguments('}'));
// Scanner guarantees a closing bracket.
next = begin.endGroup;
while (token.next != next) {
token = token.next;
}
}
break;
}
token = next;
}
if (parameterCount == 0) {
rewriteAndRecover(
token,
fasta.messageEmptyNamedParameterList,
new SyntheticStringToken(
TokenType.IDENTIFIER, '', token.next.charOffset, 0));
token =
parseFormalParameter(token, FormalParameterKind.optionalNamed, kind);
++parameterCount;
}
token = token.next;
assert(optional('}', token));
listener.endOptionalFormalParameters(parameterCount, begin, token);
return token;
}
/// ```
/// qualified:
/// identifier qualifiedRest*
/// ;
/// ```
Token parseQualified(Token token, IdentifierContext context,
IdentifierContext continuationContext) {
token = ensureIdentifier(token, context);
while (optional('.', token.next)) {
token = parseQualifiedRest(token, continuationContext);
}
return token;
}
/// ```
/// qualifiedRestOpt:
/// qualifiedRest?
/// ;
/// ```
Token parseQualifiedRestOpt(
Token token, IdentifierContext continuationContext) {
if (optional('.', token.next)) {
return parseQualifiedRest(token, continuationContext);
} else {
return token;
}
}
/// ```
/// qualifiedRest:
/// '.' identifier
/// ;
/// ```
Token parseQualifiedRest(Token token, IdentifierContext context) {
token = token.next;
assert(optional('.', token));
Token period = token;
token = ensureIdentifier(token, context);
listener.handleQualified(period);
return token;
}
Token skipBlock(Token token) {
// The scanner ensures that `{` always has a closing `}`.
return ensureBlock(token, null, null).endGroup;
}
/// ```
/// enumType:
/// metadata 'enum' id '{' metadata id [',' metadata id]* [','] '}'
/// ;
/// ```
Token parseEnum(Token enumKeyword) {
assert(optional('enum', enumKeyword));
listener.beginEnum(enumKeyword);
Token token =
ensureIdentifier(enumKeyword, IdentifierContext.enumDeclaration);
Token leftBrace = token.next;
int count = 0;
if (optional('{', leftBrace)) {
token = leftBrace;
while (true) {
Token next = token.next;
if (optional('}', next)) {
token = next;
if (count == 0) {
reportRecoverableError(token, fasta.messageEnumDeclarationEmpty);
}
break;
}
token = parseMetadataStar(token);
token = ensureIdentifier(token, IdentifierContext.enumValueDeclaration);
next = token.next;
count++;
if (optional(',', next)) {
token = next;
} else if (optional('}', next)) {
token = next;
break;
} else {
// Recovery
Token endGroup = leftBrace.endGroup;
if (endGroup.isSynthetic) {
// The scanner did not place the synthetic '}' correctly.
token = rewriter.moveSynthetic(token, endGroup);
break;
} else if (next.isIdentifier) {
// If the next token is an identifier, assume a missing comma.
// TODO(danrubel): Consider improved recovery for missing `}`
// both here and when the scanner inserts a synthetic `}`
// for situations such as `enum Letter {a, b Letter e;`.
reportRecoverableError(
next, fasta.templateExpectedButGot.withArguments(','));
} else {
// Otherwise assume a missing `}` and exit the loop
reportRecoverableError(
next, fasta.templateExpectedButGot.withArguments('}'));
token = leftBrace.endGroup;
break;
}
}
}
} else {
// TODO(danrubel): merge this error message with missing class/mixin body
leftBrace = ensureBlock(token, fasta.templateExpectedEnumBody, null);
token = leftBrace.endGroup;
}
assert(optional('}', token));
listener.endEnum(enumKeyword, leftBrace, count);
return token;
}
Token parseClassOrNamedMixinApplication(
Token abstractToken, Token classKeyword) {
assert(optional('class', classKeyword));
Token begin = abstractToken ?? classKeyword;
listener.beginClassOrNamedMixinApplicationPrelude(begin);
Token name = ensureIdentifier(
classKeyword, IdentifierContext.classOrMixinOrExtensionDeclaration);
Token token =
computeTypeParamOrArg(name, true, true).parseVariables(name, this);
if (optional('=', token.next)) {
listener.beginNamedMixinApplication(begin, abstractToken, name);
return parseNamedMixinApplication(token, begin, classKeyword);
} else {
listener.beginClassDeclaration(begin, abstractToken, name);
return parseClass(token, begin, classKeyword, name.lexeme);
}
}
Token parseNamedMixinApplication(
Token token, Token begin, Token classKeyword) {
Token equals = token = token.next;
assert(optional('=', equals));
token = computeType(token, true).ensureTypeNotVoid(token, this);
token = parseMixinApplicationRest(token);
Token implementsKeyword = null;
if (optional('implements', token.next)) {
implementsKeyword = token.next;
token = parseTypeList(implementsKeyword);
}
token = ensureSemicolon(token);
listener.endNamedMixinApplication(
begin, classKeyword, equals, implementsKeyword, token);
return token;
}
/// Parse the portion of a class declaration (not a mixin application) that
/// follows the end of the type parameters.
///
/// ```
/// classDefinition:
/// metadata abstract? 'class' identifier typeParameters?
/// (superclass mixins?)? interfaces?
/// '{' (metadata classMemberDefinition)* '}' |
/// metadata abstract? 'class' mixinApplicationClass
/// ;
/// ```
Token parseClass(
Token token, Token begin, Token classKeyword, String className) {
Token start = token;
token = parseClassHeaderOpt(token, begin, classKeyword);
if (!optional('{', token.next)) {
// Recovery
token = parseClassHeaderRecovery(start, begin, classKeyword);
ensureBlock(token, null, 'class declaration');
}
token = parseClassOrMixinOrExtensionBody(
token, DeclarationKind.Class, className);
listener.endClassDeclaration(begin, token);
return token;
}
Token parseClassHeaderOpt(Token token, Token begin, Token classKeyword) {
token = parseClassExtendsOpt(token);
token = parseWithClauseOpt(token);
token = parseClassOrMixinImplementsOpt(token);
Token nativeToken;
if (optional('native', token.next)) {
nativeToken = token.next;
token = parseNativeClause(token);
}
listener.handleClassHeader(begin, classKeyword, nativeToken);
return token;
}
/// Recover given out-of-order clauses in a class header.
Token parseClassHeaderRecovery(Token token, Token begin, Token classKeyword) {
final Listener primaryListener = listener;
final ClassHeaderRecoveryListener recoveryListener =
new ClassHeaderRecoveryListener();
// Reparse to determine which clauses have already been parsed
// but intercept the events so they are not sent to the primary listener.
listener = recoveryListener;
token = parseClassHeaderOpt(token, begin, classKeyword);
bool hasExtends = recoveryListener.extendsKeyword != null;
bool hasImplements = recoveryListener.implementsKeyword != null;
bool hasWith = recoveryListener.withKeyword != null;
// Update the recovery listener to forward subsequent events
// to the primary listener.
recoveryListener.listener = primaryListener;
// Parse additional out-of-order clauses
Token start;
do {
start = token;
// Check for extraneous token in the middle of a class header.
token = skipUnexpectedTokenOpt(
token, const <String>['extends', 'with', 'implements', '{']);
// During recovery, clauses are parsed in the same order
// and generate the same events as in the parseClassHeader method above.
recoveryListener.clear();
if (token.next.isKeywordOrIdentifier &&
const ['extend', 'on'].contains(token.next.lexeme)) {
reportRecoverableError(
token.next, fasta.templateExpectedInstead.withArguments('extends'));
Token incorrectExtendsKeyword = token.next;
token = computeType(incorrectExtendsKeyword, true)
.ensureTypeNotVoid(incorrectExtendsKeyword, this);
listener.handleClassExtends(incorrectExtendsKeyword);
} else {
token = parseClassExtendsOpt(token);
}
if (recoveryListener.extendsKeyword != null) {
if (hasExtends) {
reportRecoverableError(
recoveryListener.extendsKeyword, fasta.messageMultipleExtends);
} else {
if (hasWith) {
reportRecoverableError(recoveryListener.extendsKeyword,
fasta.messageWithBeforeExtends);
} else if (hasImplements) {
reportRecoverableError(recoveryListener.extendsKeyword,
fasta.messageImplementsBeforeExtends);
}
hasExtends = true;
}
}
token = parseWithClauseOpt(token);
if (recoveryListener.withKeyword != null) {
if (hasWith) {
reportRecoverableError(
recoveryListener.withKeyword, fasta.messageMultipleWith);
} else {
if (hasImplements) {
reportRecoverableError(recoveryListener.withKeyword,
fasta.messageImplementsBeforeWith);
}
hasWith = true;
}
}
token = parseClassOrMixinImplementsOpt(token);
if (recoveryListener.implementsKeyword != null) {
if (hasImplements) {
reportRecoverableError(recoveryListener.implementsKeyword,
fasta.messageMultipleImplements);
} else {
hasImplements = true;
}
}
listener.handleRecoverClassHeader();
// Exit if a class body is detected, or if no progress has been made
} while (!optional('{', token.next) && start != token);
listener = primaryListener;
return token;
}
Token parseClassExtendsOpt(Token token) {
Token next = token.next;
if (optional('extends', next)) {
Token extendsKeyword = next;
token = computeType(next, true).ensureTypeNotVoid(next, this);
listener.handleClassExtends(extendsKeyword);
} else {
listener.handleNoType(token);
listener.handleClassExtends(null);
}
return token;
}
/// ```
/// implementsClause:
/// 'implements' typeName (',' typeName)*
/// ;
/// ```
Token parseClassOrMixinImplementsOpt(Token token) {
Token implementsKeyword;
int interfacesCount = 0;
if (optional('implements', token.next)) {
implementsKeyword = token.next;
do {
token =
computeType(token.next, true).ensureTypeNotVoid(token.next, this);
++interfacesCount;
} while (optional(',', token.next));
}
listener.handleClassOrMixinImplements(implementsKeyword, interfacesCount);
return token;
}
/// Parse a mixin declaration.
///
/// ```
/// mixinDeclaration:
/// metadata? 'mixin' [SimpleIdentifier] [TypeParameterList]?
/// [OnClause]? [ImplementsClause]? '{' [ClassMember]* '}'
/// ;
/// ```
Token parseMixin(Token mixinKeyword) {
assert(optional('mixin', mixinKeyword));
listener.beginClassOrNamedMixinApplicationPrelude(mixinKeyword);
Token name = ensureIdentifier(
mixinKeyword, IdentifierContext.classOrMixinOrExtensionDeclaration);
Token headerStart =
computeTypeParamOrArg(name, true, true).parseVariables(name, this);
listener.beginMixinDeclaration(mixinKeyword, name);
Token token = parseMixinHeaderOpt(headerStart, mixinKeyword);
if (!optional('{', token.next)) {
// Recovery
token = parseMixinHeaderRecovery(token, mixinKeyword, headerStart);
ensureBlock(token, null, 'mixin declaration');
}
token = parseClassOrMixinOrExtensionBody(
token, DeclarationKind.Mixin, name.lexeme);
listener.endMixinDeclaration(mixinKeyword, token);
return token;
}
Token parseMixinHeaderOpt(Token token, Token mixinKeyword) {
token = parseMixinOnOpt(token);
token = parseClassOrMixinImplementsOpt(token);
listener.handleMixinHeader(mixinKeyword);
return token;
}
Token parseMixinHeaderRecovery(
Token token, Token mixinKeyword, Token headerStart) {
final Listener primaryListener = listener;
final MixinHeaderRecoveryListener recoveryListener =
new MixinHeaderRecoveryListener();
// Reparse to determine which clauses have already been parsed
// but intercept the events so they are not sent to the primary listener.
listener = recoveryListener;
token = parseMixinHeaderOpt(headerStart, mixinKeyword);
bool hasOn = recoveryListener.onKeyword != null;
bool hasImplements = recoveryListener.implementsKeyword != null;
// Update the recovery listener to forward subsequent events
// to the primary listener.
recoveryListener.listener = primaryListener;
// Parse additional out-of-order clauses
Token start;
do {
start = token;
// Check for extraneous token in the middle of a class header.
token = skipUnexpectedTokenOpt(
token, const <String>['on', 'implements', '{']);
// During recovery, clauses are parsed in the same order and
// generate the same events as in the parseMixinHeaderOpt method above.
recoveryListener.clear();
if (token.next.isKeywordOrIdentifier &&
const ['extend', 'extends'].contains(token.next.lexeme)) {
reportRecoverableError(
token.next, fasta.templateExpectedInstead.withArguments('on'));
token = parseMixinOn(token);
} else {
token = parseMixinOnOpt(token);
}
if (recoveryListener.onKeyword != null) {
if (hasOn) {
reportRecoverableError(
recoveryListener.onKeyword, fasta.messageMultipleOnClauses);
} else {
if (hasImplements) {
reportRecoverableError(
recoveryListener.onKeyword, fasta.messageImplementsBeforeOn);
}
hasOn = true;
}
}
token = parseClassOrMixinImplementsOpt(token);
if (recoveryListener.implementsKeyword != null) {
if (hasImplements) {
reportRecoverableError(recoveryListener.implementsKeyword,
fasta.messageMultipleImplements);
} else {
hasImplements = true;
}
}
listener.handleRecoverMixinHeader();
// Exit if a mixin body is detected, or if no progress has been made
} while (!optional('{', token.next) && start != token);
listener = primaryListener;
return token;
}
/// ```
/// onClause:
/// 'on' typeName (',' typeName)*
/// ;
/// ```
Token parseMixinOnOpt(Token token) {
if (!optional('on', token.next)) {
listener.handleMixinOn(null, 0);
return token;
}
return parseMixinOn(token);
}
Token parseMixinOn(Token token) {
Token onKeyword = token.next;
// During recovery, the [onKeyword] can be "extend" or "extends"
assert(optional('on', onKeyword) ||
optional('extends', onKeyword) ||
onKeyword.lexeme == 'extend');
int typeCount = 0;
do {
token = computeType(token.next, true).ensureTypeNotVoid(token.next, this);
++typeCount;
} while (optional(',', token.next));
listener.handleMixinOn(onKeyword, typeCount);
return token;
}
/// ```
/// 'extension' <identifier>? <typeParameters>? 'on' <type> '?'?
// `{'
// <memberDeclaration>*
// `}'
/// ```
Token parseExtension(Token extensionKeyword) {
assert(optional('extension', extensionKeyword));
Token token = extensionKeyword;
listener.beginExtensionDeclarationPrelude(extensionKeyword);
Token name = token.next;
if (name.isIdentifier && !optional('on', name)) {
token = name;
if (name.type.isBuiltIn) {
reportRecoverableErrorWithToken(
token, fasta.templateBuiltInIdentifierInDeclaration);
}
} else {
name = null;
}
token = computeTypeParamOrArg(token, true).parseVariables(token, this);
listener.beginExtensionDeclaration(extensionKeyword, name);
Token onKeyword = token.next;
if (!optional('on', onKeyword)) {
// Recovery
if (optional('extends', onKeyword) ||
optional('implements', onKeyword) ||
optional('with', onKeyword)) {
reportRecoverableError(
onKeyword, fasta.templateExpectedInstead.withArguments('on'));
} else {
reportRecoverableError(
token, fasta.templateExpectedAfterButGot.withArguments('on'));
onKeyword = rewriter.insertSyntheticKeyword(token, Keyword.ON);
}
}
TypeInfo typeInfo = computeType(onKeyword, true);
token = typeInfo.ensureTypeOrVoid(onKeyword, this);
if (!optional('{', token.next)) {
// Recovery
Token next = token.next;
while (!next.isEof) {
if (optional(',', next) ||
optional('extends', next) ||
optional('implements', next) ||
optional('on', next) ||
optional('with', next)) {
// Report an error and skip `,` or specific keyword
// optionally followed by an identifier
reportRecoverableErrorWithToken(next, fasta.templateUnexpectedToken);
token = next;
next = token.next;
if (next.isIdentifier) {
token = next;
next = token.next;
}
} else {
break;
}
}
ensureBlock(token, null, 'extension declaration');
}
token = parseClassOrMixinOrExtensionBody(
token, DeclarationKind.Extension, name?.lexeme);
listener.endExtensionDeclaration(extensionKeyword, onKeyword, token);
return token;
}
Token parseStringPart(Token token) {
Token next = token.next;
if (next.kind != STRING_TOKEN) {
reportRecoverableErrorWithToken(next, fasta.templateExpectedString);
next = rewriter.insertToken(token,
new SyntheticStringToken(TokenType.STRING, '', next.charOffset));
}
listener.handleStringPart(next);
return next;
}
/// Insert a synthetic identifier after the given [token] and create an error
/// message based on the given [context]. Return the synthetic identifier that
/// was inserted.
Token insertSyntheticIdentifier(Token token, IdentifierContext context,
{Message message, Token messageOnToken}) {
Token next = token.next;
reportRecoverableError(messageOnToken ?? next,
message ?? context.recoveryTemplate.withArguments(next));
return rewriter.insertSyntheticIdentifier(token);
}
/// Parse a simple identifier at the given [token], and return the identifier
/// that was parsed.
///
/// If the token is not an identifier, or is not appropriate for use as an
/// identifier in the given [context], create a synthetic identifier, report
/// an error, and return the synthetic identifier.
Token ensureIdentifier(Token token, IdentifierContext context) {
assert(context != null);
Token identifier = token.next;
if (identifier.kind != IDENTIFIER_TOKEN) {
identifier = context.ensureIdentifier(token, this);
assert(identifier != null);
assert(identifier.isKeywordOrIdentifier);
}
listener.handleIdentifier(identifier, context);
return identifier;
}
bool notEofOrValue(String value, Token token) {
return !identical(token.kind, EOF_TOKEN) &&
!identical(value, token.stringValue);
}
Token parseTypeVariablesOpt(Token token) {
return computeTypeParamOrArg(token, true).parseVariables(token, this);
}
/// Parse a top level field or function.
///
/// This method is only invoked from outside the parser. As a result, this
/// method takes the next token to be consumed rather than the last consumed
/// token and returns the token after the last consumed token rather than the
/// last consumed token.
Token parseTopLevelMember(Token token) {
token = parseMetadataStar(syntheticPreviousToken(token));
return parseTopLevelMemberImpl(token).next;
}
Token parseTopLevelMemberImpl(Token token) {
Token beforeStart = token;
Token next = token.next;
listener.beginTopLevelMember(next);
Token externalToken;
Token lateToken;
Token varFinalOrConst;
if (isModifier(next)) {
if (optional('external', next)) {
externalToken = token = next;
next = token.next;
}
if (isModifier(next)) {
if (optional('final', next)) {
varFinalOrConst = token = next;
next = token.next;
} else if (optional('var', next)) {
varFinalOrConst = token = next;
next = token.next;
} else if (optional('const', next)) {
varFinalOrConst = token = next;
next = token.next;
} else if (optional('late', next)) {
lateToken = token = next;
next = token.next;
if (isModifier(next) && optional('final', next)) {
varFinalOrConst = token = next;
next = token.next;
}
}
if (isModifier(next)) {
// Recovery
if (varFinalOrConst != null &&
(optional('final', next) ||
optional('var', next) ||
optional('const', next))) {
// If another `var`, `final`, or `const` then fall through
// to parse that as part of the next top level declaration.
} else {
ModifierRecoveryContext context = new ModifierRecoveryContext(this)
..externalToken = externalToken
..lateToken = lateToken
..varFinalOrConst = varFinalOrConst;
token = context.parseTopLevelModifiers(token);
next = token.next;
externalToken = context.externalToken;
lateToken = context.lateToken;
varFinalOrConst = context.varFinalOrConst;
context = null;
}
}
}
}
Token beforeType = token;
TypeInfo typeInfo = computeType(token, false, true);
token = typeInfo.skipType(token);
next = token.next;
Token getOrSet;
String value = next.stringValue;
if (identical(value, 'get') || identical(value, 'set')) {
if (next.next.isIdentifier) {
getOrSet = token = next;
next = token.next;
}
}
if (next.type != TokenType.IDENTIFIER) {
value = next.stringValue;
if (identical(value, 'factory') || identical(value, 'operator')) {
// `factory` and `operator` can be used as an identifier.
value = next.next.stringValue;
if (getOrSet == null &&
!identical(value, '(') &&
!identical(value, '{') &&
!identical(value, '<') &&
!identical(value, '=>') &&
!identical(value, '=') &&
!identical(value, ';') &&
!identical(value, ',')) {
// Recovery
value = next.stringValue;
if (identical(value, 'factory')) {
reportRecoverableError(
next, fasta.messageFactoryTopLevelDeclaration);
} else {
reportRecoverableError(next, fasta.messageTopLevelOperator);
if (next.next.isOperator) {
token = next;
next = token.next;
if (optional('(', next.next)) {
rewriter.insertSyntheticIdentifier(
next, '#synthetic_identifier_${next.charOffset}');
}
}
}
listener.handleInvalidTopLevelDeclaration(next);
return next;
}
// Fall through and continue parsing
} else if (!next.isIdentifier) {
// Recovery
if (next.isKeyword) {
// Fall through to parse the keyword as the identifier.
// ensureIdentifier will report the error.
} else if (token == beforeStart) {
// Ensure we make progress.
return parseInvalidTopLevelDeclaration(token);
} else {
// Looks like a declaration missing an identifier.
// Insert synthetic identifier and fall through.
insertSyntheticIdentifier(token, IdentifierContext.methodDeclaration);
next = token.next;
}
}
}
// At this point, `token` is beforeName.
next = next.next;
value = next.stringValue;
if (getOrSet != null ||
identical(value, '(') ||
identical(value, '{') ||
identical(value, '<') ||
identical(value, '.') ||
identical(value, '=>')) {
if (varFinalOrConst != null) {
if (optional('var', varFinalOrConst)) {
reportRecoverableError(varFinalOrConst, fasta.messageVarReturnType);
} else {
reportRecoverableErrorWithToken(
varFinalOrConst, fasta.templateExtraneousModifier);
}
} else if (lateToken != null) {
reportRecoverableErrorWithToken(
lateToken, fasta.templateExtraneousModifier);
}
return parseTopLevelMethod(beforeStart, externalToken, beforeType,
typeInfo, getOrSet, token.next);
}
if (getOrSet != null) {
reportRecoverableErrorWithToken(
getOrSet, fasta.templateExtraneousModifier);
}
return parseFields(
beforeStart,
externalToken,
null,
null,
lateToken,
varFinalOrConst,
beforeType,
typeInfo,
token.next,
DeclarationKind.TopLevel);
}
Token parseFields(
Token beforeStart,
Token externalToken,
Token staticToken,
Token covariantToken,
Token lateToken,
Token varFinalOrConst,
Token beforeType,
TypeInfo typeInfo,
Token name,
DeclarationKind kind) {
if (externalToken != null) {
reportRecoverableError(externalToken, fasta.messageExternalField);
}
if (covariantToken != null) {
if (varFinalOrConst != null && optional('final', varFinalOrConst)) {
reportRecoverableError(covariantToken, fasta.messageFinalAndCovariant);
covariantToken = null;
}
}
if (typeInfo == noType) {
if (varFinalOrConst == null && lateToken == null) {
reportRecoverableError(name, fasta.messageMissingConstFinalVarOrType);
}
} else {
if (varFinalOrConst != null && optional('var', varFinalOrConst)) {
reportRecoverableError(varFinalOrConst, fasta.messageTypeAfterVar);
}
}
Token token = typeInfo.parseType(beforeType, this);
assert(token.next == name);
IdentifierContext context = kind == DeclarationKind.TopLevel
? IdentifierContext.topLevelVariableDeclaration
: IdentifierContext.fieldDeclaration;
Token firstName = name = ensureIdentifier(token, context);
int fieldCount = 1;
token =
parseFieldInitializerOpt(name, name, lateToken, varFinalOrConst, kind);
while (optional(',', token.next)) {
name = ensureIdentifier(token.next, context);
token = parseFieldInitializerOpt(
name, name, lateToken, varFinalOrConst, kind);
++fieldCount;
}
Token semicolon = token.next;
if (optional(';', semicolon)) {
token = semicolon;
} else {
// Recovery
if (kind == DeclarationKind.TopLevel &&
beforeType.next.isIdentifier &&
beforeType.next.lexeme == 'extension') {
// Looks like an extension method
// TODO(danrubel): Remove when extension methods are enabled by default
// because then 'extension' will be interpreted as a built-in
// and this code will never be executed
reportRecoverableError(
beforeType.next,
fasta.templateExperimentNotEnabled
.withArguments('extension-methods'));
token = rewriter.insertSyntheticToken(token, TokenType.SEMICOLON);
} else {
token = ensureSemicolon(token);
}
}
switch (kind) {
case DeclarationKind.TopLevel:
listener.endTopLevelFields(staticToken, covariantToken, lateToken,
varFinalOrConst, fieldCount, beforeStart.next, token);
break;
case DeclarationKind.Class:
listener.endClassFields(staticToken, covariantToken, lateToken,
varFinalOrConst, fieldCount, beforeStart.next, token);
break;
case DeclarationKind.Mixin:
listener.endMixinFields(staticToken, covariantToken, lateToken,
varFinalOrConst, fieldCount, beforeStart.next, token);
break;
case DeclarationKind.Extension:
if (staticToken == null) {
reportRecoverableError(
firstName, fasta.messageExtensionDeclaresInstanceField);
}
listener.endExtensionFields(staticToken, covariantToken, lateToken,
varFinalOrConst, fieldCount, beforeStart.next, token);
break;
}
return token;
}
Token parseTopLevelMethod(Token beforeStart, Token externalToken,
Token beforeType, TypeInfo typeInfo, Token getOrSet, Token name) {
listener.beginTopLevelMethod(beforeStart, externalToken);
Token token = typeInfo.parseType(beforeType, this);
assert(token.next == (getOrSet ?? name) || token.next.isEof);
name = ensureIdentifier(
getOrSet ?? token, IdentifierContext.topLevelFunctionDeclaration);
bool isGetter = false;
if (getOrSet == null) {
token = parseMethodTypeVar(name);
} else {
isGetter = optional("get", getOrSet);
token = name;
listener.handleNoTypeVariables(token.next);
}
token = parseGetterOrFormalParameters(
token, name, isGetter, MemberKind.TopLevelMethod);
AsyncModifier savedAsyncModifier = asyncState;
Token asyncToken = token.next;
token = parseAsyncModifierOpt(token);
if (getOrSet != null && !inPlainSync && optional("set", getOrSet)) {
reportRecoverableError(asyncToken, fasta.messageSetterNotSync);
}
bool isExternal = externalToken != null;
if (isExternal && !optional(';', token.next)) {
reportRecoverableError(
externalToken, fasta.messageExternalMethodWithBody);
}
token = parseFunctionBody(token, false, isExternal);
asyncState = savedAsyncModifier;
listener.endTopLevelMethod(beforeStart.next, getOrSet, token);
return token;
}
Token parseMethodTypeVar(Token name) {
if (!optional('<', name.next)) {
return noTypeParamOrArg.parseVariables(name, this);
}
TypeParamOrArgInfo typeVar = computeTypeParamOrArg(name, true);
Token token = typeVar.parseVariables(name, this);
if (optional('=', token.next)) {
// Recovery
token = token.next;
reportRecoverableErrorWithToken(token, fasta.templateUnexpectedToken);
}
return token;
}
Token parseFieldInitializerOpt(Token token, Token name, Token lateToken,
Token varFinalOrConst, DeclarationKind kind) {
Token next = token.next;
if (optional('=', next)) {
Token assignment = next;
listener.beginFieldInitializer(next);
token = parseExpression(next);
listener.endFieldInitializer(assignment, token.next);
} else {
if (varFinalOrConst != null && !name.isSynthetic) {
if (optional("const", varFinalOrConst)) {
reportRecoverableError(
name,
fasta.templateConstFieldWithoutInitializer
.withArguments(name.lexeme));
} else if (kind == DeclarationKind.TopLevel &&
optional("final", varFinalOrConst) &&
lateToken == null) {
reportRecoverableError(
name,
fasta.templateFinalFieldWithoutInitializer
.withArguments(name.lexeme));
}
}
listener.handleNoFieldInitializer(token.next);
}
return token;
}
Token parseVariableInitializerOpt(Token token) {
if (optional('=', token.next)) {
Token assignment = token.next;
listener.beginVariableInitializer(assignment);
token = parseExpression(assignment);
listener.endVariableInitializer(assignment);
} else {
listener.handleNoVariableInitializer(token.next);
}
return token;
}
Token parseInitializersOpt(Token token) {
if (optional(':', token.next)) {
return parseInitializers(token.next);
} else {
listener.handleNoInitializers();
return token;
}
}
/// ```
/// initializers:
/// ':' initializerListEntry (',' initializerListEntry)*
/// ;
/// ```
Token parseInitializers(Token token) {
Token begin = token;
assert(optional(':', begin));
listener.beginInitializers(begin);
int count = 0;
bool old = mayParseFunctionExpressions;
mayParseFunctionExpressions = false;
Token next = begin;
while (true) {
token = parseInitializer(next);
++count;
next = token.next;
if (!optional(',', next)) {
// Recovery: Found an identifier which could be
// 1) missing preceding `,` thus it's another initializer, or
// 2) missing preceding `;` thus it's a class member, or
// 3) missing preceding '{' thus it's a statement
if (optional('assert', next)) {
next = next.next;
if (!optional('(', next)) {
break;
}
// Looks like assert expression ... fall through to insert comma
} else if (!next.isIdentifier && !optional('this', next)) {
// An identifier that wasn't an initializer. Break.
break;
} else {
if (optional('this', next)) {
next = next.next;
if (!optional('.', next)) {
break;
}
next = next.next;
if (!next.isIdentifier && !optional('assert', next)) {
break;
}
}
next = next.next;
if (!optional('=', next)) {
break;
}
// Looks like field assignment... fall through to insert comma
}
// TODO(danrubel): Consider enhancing this to indicate that we are
// expecting one of `,` or `;` or `{`
reportRecoverableError(
token, fasta.templateExpectedAfterButGot.withArguments(','));
next = rewriter.insertSyntheticToken(token, TokenType.COMMA);
}
}
mayParseFunctionExpressions = old;
listener.endInitializers(count, begin, token.next);
return token;
}
/// ```
/// initializerListEntry:
/// 'super' ('.' identifier)? arguments |
/// fieldInitializer |
/// assertion
/// ;
///
/// fieldInitializer:
/// ('this' '.')? identifier '=' conditionalExpression cascadeSection*
/// ;
/// ```
Token parseInitializer(Token token) {
Token next = token.next;
listener.beginInitializer(next);
Token beforeExpression = token;
if (optional('assert', next)) {
token = parseAssert(token, Assert.Initializer);
listener.endInitializer(token.next);
return token;
} else if (optional('super', next)) {
return parseSuperInitializerExpression(token);
} else if (optional('this', next)) {
token = next;
next = token.next;
if (optional('.', next)) {
token = next;
next = token.next;
if (next.isIdentifier) {
token = next;
} else {
// Recovery
token = insertSyntheticIdentifier(
token, IdentifierContext.fieldInitializer);
}
next = token.next;
if (optional('=', next)) {
return parseInitializerExpressionRest(beforeExpression);
}
}
if (optional('(', next)) {
token = parseInitializerExpressionRest(beforeExpression);
next = token.next;
if (optional('{', next) || optional('=>', next)) {
reportRecoverableError(
next, fasta.messageRedirectingConstructorWithBody);
}
return token;
}
// Recovery
if (optional('this', token)) {
// TODO(danrubel): Consider a better error message indicating that
// `this.<fieldname>=` is expected.
reportRecoverableError(
next, fasta.templateExpectedButGot.withArguments('.'));
rewriter.insertSyntheticToken(token, TokenType.PERIOD);
token = rewriter.insertSyntheticIdentifier(token.next);
next = token.next;
}
// Fall through to recovery
} else if (next.isIdentifier) {
Token next2 = next.next;
if (optional('=', next2)) {
return parseInitializerExpressionRest(token);
}
// Recovery: If this looks like an expression,
// then fall through to insert the LHS and `=` of the assignment,
// otherwise insert an `=` and synthetic identifier.
if (!next2.isOperator && !optional('.', next2)) {
token = rewriter.insertSyntheticToken(next, TokenType.EQ);
token = insertSyntheticIdentifier(token, IdentifierContext.expression,
message: fasta.messageMissingAssignmentInInitializer,
messageOnToken: next);
return parseInitializerExpressionRest(beforeExpression);
}
} else {
// Recovery: Insert a synthetic assignment.
token = insertSyntheticIdentifier(
token, IdentifierContext.fieldInitializer,
message: fasta.messageExpectedAnInitializer, messageOnToken: token);
token = rewriter.insertSyntheticToken(token, TokenType.EQ);
token = rewriter.insertSyntheticIdentifier(token);
return parseInitializerExpressionRest(beforeExpression);
}
// Recovery:
// Insert a synthetic identifier and assignment operator
// to ensure that the expression is indeed an assignment.
// Failing to do so causes this test to fail:
// pkg/front_end/testcases/regress/issue_31192.dart
// TODO(danrubel): Investigate better recovery.
token = insertSyntheticIdentifier(
beforeExpression, IdentifierContext.fieldInitializer,
message: fasta.messageMissingAssignmentInInitializer);
rewriter.insertSyntheticToken(token, TokenType.EQ);
return parseInitializerExpressionRest(beforeExpression);
}
/// Parse the `super` initializer:
/// ```
/// 'super' ('.' identifier)? arguments ;
/// ```
Token parseSuperInitializerExpression(final Token start) {
Token token = start.next;
assert(optional('super', token));
Token next = token.next;
if (optional('.', next)) {
token = next;
next = token.next;
if (next.kind != IDENTIFIER_TOKEN) {
next = IdentifierContext.expressionContinuation
.ensureIdentifier(token, this);
}
token = next;
next = token.next;
}
if (!optional('(', next)) {
// Recovery
if (optional('?.', next)) {
// An error for `super?.` is reported in parseSuperExpression.
token = next;
next = token.next;
if (!next.isIdentifier) {
// Insert a synthetic identifier but don't report another error.
next = rewriter.insertSyntheticIdentifier(token);
}
token = next;
next = token.next;
}
if (optional('=', next)) {
if (optional('super', token)) {
// parseExpression will report error on assignment to super
} else {
reportRecoverableError(
token, fasta.messageFieldInitializedOutsideDeclaringClass);
}
} else if (!optional('(', next)) {
reportRecoverableError(
next, fasta.templateExpectedAfterButGot.withArguments('('));
rewriter.insertParens(token, false);
}
}
return parseInitializerExpressionRest(start);
}
Token parseInitializerExpressionRest(Token token) {
token = parseExpression(token);
listener.endInitializer(token.next);
return token;
}
/// If the next token is an opening curly brace, return it. Otherwise, use the
/// given [template] or [missingBlockName] to report an error, insert an
/// opening and a closing curly brace, and return the newly inserted opening
/// curly brace. If [template] and [missingBlockName] are `null`, then use
/// a default error message instead.
Token ensureBlock(
Token token,
Template<Message Function(Token token)> template,
String missingBlockName) {
Token next = token.next;
if (optional('{', next)) return next;
if (template == null) {
if (missingBlockName == null) {
// TODO(danrubel): rename ExpectedButGot to ExpectedBefore
reportRecoverableError(
next, fasta.templateExpectedButGot.withArguments('{'));
} else {
// TODO(danrubel): rename ExpectedClassOrMixinBody
// to ExpectedDeclarationOrClauseBody
reportRecoverableError(
token,
fasta.templateExpectedClassOrMixinBody
.withArguments(missingBlockName));
}
} else {
reportRecoverableError(next, template.withArguments(next));
}
return insertBlock(token);
}
Token insertBlock(Token token) {
Token next = token.next;
BeginToken beginGroup = rewriter.insertToken(token,
new SyntheticBeginToken(TokenType.OPEN_CURLY_BRACKET, next.offset));
Token endGroup = rewriter.insertToken(beginGroup,
new SyntheticToken(TokenType.CLOSE_CURLY_BRACKET, next.offset));
beginGroup.endGroup = endGroup;
return beginGroup;
}
/// If the next token is a closing parenthesis, return it.
/// Otherwise, report an error and return the closing parenthesis
/// associated with the specified open parenthesis.
Token ensureCloseParen(Token token, Token openParen) {
Token next = token.next;
if (optional(')', next)) {
return next;
}
if (openParen.endGroup.isSynthetic) {
// Scanner has already reported a missing `)` error,
// but placed the `)` in the wrong location, so move it.
return rewriter.moveSynthetic(token, openParen.endGroup);
}
// TODO(danrubel): Pass in context for better error message.
reportRecoverableError(
next, fasta.templateExpectedButGot.withArguments(')'));
// Scanner guarantees a closing parenthesis
// TODO(danrubel): Improve recovery by having callers parse tokens
// between `token` and `openParen.endGroup`.
return openParen.endGroup;
}
/// If the next token is a colon, return it. Otherwise, report an
/// error, insert a synthetic colon, and return the inserted colon.
Token ensureColon(Token token) {
Token next = token.next;
if (optional(':', next)) return next;
Message message = fasta.templateExpectedButGot.withArguments(':');
Token newToken = new SyntheticToken(TokenType.COLON, next.charOffset);
return rewriteAndRecover(token, message, newToken);
}
/// If the token after [token] is a not literal string,
/// then insert a synthetic literal string.
/// Call `parseLiteralString` and return the result.
Token ensureLiteralString(Token token) {
Token next = token.next;
if (!identical(next.kind, STRING_TOKEN)) {
Message message = fasta.templateExpectedString.withArguments(next);
Token newToken =
new SyntheticStringToken(TokenType.STRING, '""', next.charOffset, 0);
rewriteAndRecover(token, message, newToken);
}
return parseLiteralString(token);
}
/// If the token after [token] is a semi-colon, return it.
/// Otherwise, report an error, insert a synthetic semi-colon,
/// and return the inserted semi-colon.
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.
Token next = token.next;
if (optional(';', next)) return next;
// Find a token on the same line as where the ';' should be inserted.
// Reporting the error on this token makes it easier
// for users to understand and fix the error.
reportRecoverableError(findPreviousNonZeroLengthToken(token),
fasta.templateExpectedAfterButGot.withArguments(';'));
return rewriter.insertSyntheticToken(token, TokenType.SEMICOLON);
}
/// Report an error at the token after [token] that has the given [message].
/// Insert the [newToken] after [token] and return [newToken].
Token rewriteAndRecover(Token token, Message message, Token newToken) {
reportRecoverableError(token.next, message);
return rewriter.insertToken(token, newToken);
}
/// Replace the token after [token] with `[` followed by `]`
/// and return [token].
Token rewriteSquareBrackets(Token token) {
Token next = token.next;
assert(optional('[]', next));
Token replacement;
if (next.isSynthetic) {
replacement = link(
new SyntheticBeginToken(TokenType.OPEN_SQUARE_BRACKET, next.offset),
new SyntheticToken(TokenType.CLOSE_SQUARE_BRACKET, next.offset));
} else {
replacement = link(
new BeginToken(TokenType.OPEN_SQUARE_BRACKET, next.offset),
new Token(TokenType.CLOSE_SQUARE_BRACKET, next.offset + 1));
}
rewriter.replaceTokenFollowing(token, replacement);
return token;
}
/// Report the given token as unexpected and return the next token if the next
/// token is one of the [expectedNext], otherwise just return the given token.
Token skipUnexpectedTokenOpt(Token token, List<String> expectedNext) {
Token next = token.next;
if (next.keyword == null) {
final String nextValue = next.next.stringValue;
for (String expectedValue in expectedNext) {
if (identical(nextValue, expectedValue)) {
reportRecoverableErrorWithToken(next, fasta.templateUnexpectedToken);
return next;
}
}
}
return token;
}
Token parseNativeClause(Token token) {
Token nativeToken = token = token.next;
assert(optional('native', nativeToken));
bool hasName = false;
if (token.next.kind == STRING_TOKEN) {
hasName = true;
token = parseLiteralString(token);
}
listener.handleNativeClause(nativeToken, hasName);
reportRecoverableError(
nativeToken, fasta.messageNativeClauseShouldBeAnnotation);
return token;
}
Token skipClassOrMixinOrExtensionBody(Token token) {
// The scanner ensures that `{` always has a closing `}`.
return ensureBlock(token, null, null);
}
/// ```
/// classBody:
/// '{' classMember* '}'
/// ;
/// ```
Token parseClassOrMixinOrExtensionBody(
Token token, DeclarationKind kind, String enclosingDeclarationName) {
Token begin = token = token.next;
assert(optional('{', token));
listener.beginClassOrMixinBody(kind, token);
int count = 0;
while (notEofOrValue('}', token.next)) {
token = parseClassOrMixinOrExtensionMemberImpl(
token, kind, enclosingDeclarationName);
++count;
}
token = token.next;
assert(token.isEof || optional('}', token));
listener.endClassOrMixinBody(kind, count, begin, token);
return token;
}
bool isUnaryMinus(Token token) =>
token.kind == IDENTIFIER_TOKEN &&
token.lexeme == 'unary' &&
optional('-', token.next);
/// Parse a class member.
///
/// This method is only invoked from outside the parser. As a result, this
/// method takes the next token to be consumed rather than the last consumed
/// token and returns the token after the last consumed token rather than the
/// last consumed token.
Token parseClassMember(Token token, String className) {
return parseClassOrMixinOrExtensionMemberImpl(
syntheticPreviousToken(token), DeclarationKind.Class, className)
.next;
}
/// Parse a mixin member.
///
/// This method is only invoked from outside the parser. As a result, this
/// method takes the next token to be consumed rather than the last consumed
/// token and returns the token after the last consumed token rather than the
/// last consumed token.
Token parseMixinMember(Token token, String mixinName) {
return parseClassOrMixinOrExtensionMemberImpl(
syntheticPreviousToken(token), DeclarationKind.Mixin, mixinName)
.next;
}
/// Parse an extension member.
///
/// This method is only invoked from outside the parser. As a result, this
/// method takes the next token to be consumed rather than the last consumed
/// token and returns the token after the last consumed token rather than the
/// last consumed token.
Token parseExtensionMember(Token token, String extensionName) {
return parseClassOrMixinOrExtensionMemberImpl(syntheticPreviousToken(token),
DeclarationKind.Extension, extensionName)
.next;
}
/// ```
/// classMember:
/// fieldDeclaration |
/// constructorDeclaration |
/// methodDeclaration
/// ;
///
/// mixinMember:
/// fieldDeclaration |
/// methodDeclaration
/// ;
///
/// extensionMember:
/// staticFieldDeclaration |
/// methodDeclaration
/// ;
/// ```
Token parseClassOrMixinOrExtensionMemberImpl(
Token token, DeclarationKind kind, String enclosingDeclarationName) {
Token beforeStart = token = parseMetadataStar(token);
Token covariantToken;
Token externalToken;
Token lateToken;
Token staticToken;
Token varFinalOrConst;
Token next = token.next;
if (isModifier(next)) {
if (optional('external', next)) {
externalToken = token = next;
next = token.next;
}
if (isModifier(next)) {
if (optional('static', next)) {
staticToken = token = next;
next = token.next;
} else if (optional('covariant', next)) {
covariantToken = token = next;
next = token.next;
}
if (isModifier(next)) {
if (optional('final', next)) {
varFinalOrConst = token = next;
next = token.next;
} else if (optional('var', next)) {
varFinalOrConst = token = next;
next = token.next;
} else if (optional('const', next) && covariantToken == null) {
varFinalOrConst = token = next;
next = token.next;
} else if (optional('late', next)) {
lateToken = token = next;
next = token.next;
if (isModifier(next) && optional('final', next)) {
varFinalOrConst = token = next;
next = token.next;
}
}
if (isModifier(next)) {
ModifierRecoveryContext context = new ModifierRecoveryContext(this)
..covariantToken = covariantToken
..externalToken = externalToken
..lateToken = lateToken
..staticToken = staticToken
..varFinalOrConst = varFinalOrConst;
token = context.parseClassMemberModifiers(token);
next = token.next;
covariantToken = context.covariantToken;
externalToken = context.externalToken;
lateToken = context.lateToken;
staticToken = context.staticToken;
varFinalOrConst = context.varFinalOrConst;
context = null;
}
}
}
}
listener.beginMember();
Token beforeType = token;
TypeInfo typeInfo = computeType(token, false, true);
token = typeInfo.skipType(token);
next = token.next;
Token getOrSet;
if (next.type != TokenType.IDENTIFIER) {
String value = next.stringValue;
if (identical(value, 'get') || identical(value, 'set')) {
if (next.next.isIdentifier) {
getOrSet = token = next;
next = token.next;
}
// Fall through to continue parsing `get` or `set` as an identifier.
} else if (identical(value, 'factory')) {
Token next2 = next.next;
if (next2.isIdentifier || next2.isModifier) {
if (beforeType != token) {
reportRecoverableError(token, fasta.messageTypeBeforeFactory);
}
token = parseFactoryMethod(token, kind, beforeStart, externalToken,
staticToken ?? covariantToken, varFinalOrConst);
listener.endMember();
return token;
}
// Fall through to continue parsing `factory` as an identifier.
} else if (identical(value, 'operator')) {
Token next2 = next.next;
TypeParamOrArgInfo typeParam = computeTypeParamOrArg(next);
// `operator` can be used as an identifier as in
// `int operator<T>()` or `int operator = 2`
if (next2.isUserDefinableOperator && typeParam == noTypeParamOrArg) {
token = parseMethod(
beforeStart,
externalToken,
staticToken,
covariantToken,
lateToken,
varFinalOrConst,
beforeType,
typeInfo,
getOrSet,
token.next,
kind,
enclosingDeclarationName);
listener.endMember();
return token;
} else if (optional('===', next2) ||
optional('!==', next2) ||
(next2.isOperator &&
!optional('=', next2) &&
!optional('<', next2))) {
// Recovery: Invalid operator
return parseInvalidOperatorDeclaration(
beforeStart,
externalToken,
staticToken,
covariantToken,
lateToken,
varFinalOrConst,
beforeType,
kind,
enclosingDeclarationName);
} else if (isUnaryMinus(next2)) {
// Recovery
token = parseMethod(
beforeStart,
externalToken,
staticToken,
covariantToken,
lateToken,
varFinalOrConst,
beforeType,
typeInfo,
getOrSet,
token.next,
kind,
enclosingDeclarationName);
listener.endMember();
return token;
}
// Fall through to continue parsing `operator` as an identifier.
} else if (!next.isIdentifier ||
(identical(value, 'typedef') &&
token == beforeStart &&
next.next.isIdentifier)) {
// Recovery
return recoverFromInvalidMember(
token,
beforeStart,
externalToken,
staticToken,
covariantToken,
lateToken,
varFinalOrConst,
beforeType,
typeInfo,
getOrSet,
kind,
enclosingDeclarationName);
}
} else if (typeInfo == noType && varFinalOrConst == null) {
Token next2 = next.next;
if (next2.isUserDefinableOperator && next2.endGroup == null) {
String value = next2.next.stringValue;
if (identical(value, '(') ||
identical(value, '{') ||
identical(value, '=>')) {
// Recovery: Missing `operator` keyword
return parseInvalidOperatorDeclaration(
beforeStart,
externalToken,
staticToken,
covariantToken,
lateToken,
varFinalOrConst,
beforeType,
kind,
enclosingDeclarationName);
}
}
}
// At this point, token is before the name, and next is the name
next = next.next;
String value = next.stringValue;
if (getOrSet != null ||
identical(value, '(') ||
identical(value, '{') ||
identical(value, '<') ||
identical(value, '.') ||
identical(value, '=>')) {
token = parseMethod(
beforeStart,
externalToken,
staticToken,
covariantToken,
lateToken,
varFinalOrConst,
beforeType,
typeInfo,
getOrSet,
token.next,
kind,
enclosingDeclarationName);
} else {
if (getOrSet != null) {
reportRecoverableErrorWithToken(
getOrSet, fasta.templateExtraneousModifier);
}
token = parseFields(
beforeStart,
externalToken,
staticToken,
covariantToken,
lateToken,
varFinalOrConst,
beforeType,
typeInfo,
token.next,
kind);
}
listener.endMember();
return token;
}
Token parseMethod(
Token beforeStart,
Token externalToken,
Token staticToken,
Token covariantToken,
Token lateToken,
Token varFinalOrConst,
Token beforeType,
TypeInfo typeInfo,
Token getOrSet,
Token name,
DeclarationKind kind,
String enclosingDeclarationName) {
if (lateToken != null) {
reportRecoverableErrorWithToken(
lateToken, fasta.templateExtraneousModifier);
}
bool isOperator = false;
if (getOrSet == null && optional('operator', name)) {
Token operator = name.next;
if (operator.isOperator ||
identical(operator.kind, EQ_EQ_EQ_TOKEN) ||
identical(operator.kind, BANG_EQ_EQ_TOKEN) ||
isUnaryMinus(operator)) {
isOperator = true;
}
}
if (staticToken != null) {
if (isOperator) {
reportRecoverableError(staticToken, fasta.messageStaticOperator);
staticToken = null;
}
} else if (covariantToken != null) {
if (getOrSet == null || optional('get', getOrSet)) {
reportRecoverableError(covariantToken, fasta.messageCovariantMember);
covariantToken = null;
}
}
if (varFinalOrConst != null) {
if (optional('const', varFinalOrConst)) {
if (getOrSet != null) {
reportRecoverableErrorWithToken(
varFinalOrConst, fasta.templateExtraneousModifier);
varFinalOrConst = null;
}
} else if (optional('var', varFinalOrConst)) {
reportRecoverableError(varFinalOrConst, fasta.messageVarReturnType);
varFinalOrConst = null;
} else {
assert(optional('final', varFinalOrConst));
reportRecoverableErrorWithToken(
varFinalOrConst, fasta.templateExtraneousModifier);
varFinalOrConst = null;
}
}
// TODO(danrubel): Consider parsing the name before calling beginMethod
// rather than passing the name token into beginMethod.
listener.beginMethod(externalToken, staticToken, covariantToken,
varFinalOrConst, getOrSet, name);
Token token = typeInfo.parseType(beforeType, this);
assert(token.next == (getOrSet ?? name));
token = getOrSet ?? token;
if (isOperator) {
token = parseOperatorName(token);
} else {
token = ensureIdentifier(token, IdentifierContext.methodDeclaration);
if (getOrSet == null) {
token = parseQualifiedRestOpt(
token, IdentifierContext.methodDeclarationContinuation);
}
}
bool isGetter = false;
if (getOrSet == null) {
token = parseMethodTypeVar(token);
} else {
isGetter = optional("get", getOrSet);
listener.handleNoTypeVariables(token.next);
}
Token beforeParam = token;
Token beforeInitializers = parseGetterOrFormalParameters(
token,
name,
isGetter,
kind == DeclarationKind.Extension
? staticToken != null
? MemberKind.ExtensionStaticMethod
: MemberKind.ExtensionNonStaticMethod
: staticToken != null
? MemberKind.StaticMethod
: MemberKind.NonStaticMethod);
token = parseInitializersOpt(beforeInitializers);
if (token == beforeInitializers) beforeInitializers = null;
AsyncModifier savedAsyncModifier = asyncState;
Token asyncToken = token.next;
token = parseAsyncModifierOpt(token);
if (getOrSet != null && !inPlainSync && optional("set", getOrSet)) {
reportRecoverableError(asyncToken, fasta.messageSetterNotSync);
}
final Token bodyStart = token.next;
if (externalToken != null) {
if (!optional(';', bodyStart)) {
reportRecoverableError(bodyStart, fasta.messageExternalMethodWithBody);
}
}
if (optional('=', bodyStart)) {
reportRecoverableError(bodyStart, fasta.messageRedirectionInNonFactory);
token = parseRedirectingFactoryBody(token);
} else {
token = parseFunctionBody(token, false,
(staticToken == null || externalToken != null) && inPlainSync);
}
asyncState = savedAsyncModifier;
bool isConstructor = false;
if (optional('.', name.next) || beforeInitializers != null) {
isConstructor = true;
if (name.lexeme != enclosingDeclarationName) {
// Recovery: The name does not match,
// but the name is prefixed or the declaration contains initializers.
// Report an error and continue with invalid name.
// TODO(danrubel): report invalid constructor name
// Currently multiple listeners report this error, but that logic should
// be removed and the error reported here instead.
}
if (getOrSet != null) {
// Recovery
if (optional('.', name.next)) {
// Unexpected get/set before constructor.
// Report an error and skip over the token.
// TODO(danrubel): report an error on get/set token
// This is currently reported by listeners other than AstBuilder.
// It should be reported here rather than in the listeners.
} else {
isConstructor = false;
if (beforeInitializers != null) {
// Unexpected initializers after get/set declaration.
// Report an error on the initializers
// and continue with the get/set declaration.
// TODO(danrubel): report invalid initializers error
// Currently multiple listeners report this error, but that logic
// should be removed and the error reported here instead.
}
}
}
} else if (name.lexeme == enclosingDeclarationName) {
if (getOrSet != null) {
// Recovery: The get/set member name is invalid.
// Report an error and continue with invalid name.
// TODO(danrubel): report invalid get/set member name
// Currently multiple listeners report this error, but that logic should
// be removed and the error reported here instead.
} else {
isConstructor = true;
}
}
if (isConstructor) {
//
// constructor
//
if (staticToken != null) {
reportRecoverableError(staticToken, fasta.messageStaticConstructor);
}
switch (kind) {
case DeclarationKind.Class:
// TODO(danrubel): Remove getOrSet from constructor events
listener.endClassConstructor(getOrSet, beforeStart.next,
beforeParam.next, beforeInitializers?.next, token);
break;
case DeclarationKind.Mixin:
reportRecoverableError(name, fasta.messageMixinDeclaresConstructor);
listener.endMixinConstructor(getOrSet, beforeStart.next,
beforeParam.next, beforeInitializers?.next, token);
break;
case DeclarationKind.Extension:
reportRecoverableError(
name, fasta.messageExtensionDeclaresConstructor);
listener.endExtensionConstructor(getOrSet, beforeStart.next,
beforeParam.next, beforeInitializers?.next, token);
break;
case DeclarationKind.TopLevel:
throw "Internal error: TopLevel constructor.";
break;
}
} else {
//
// method
//
if (varFinalOrConst != null) {
assert(optional('const', varFinalOrConst));
reportRecoverableError(varFinalOrConst, fasta.messageConstMethod);
}
switch (kind) {
case DeclarationKind.Class:
// TODO(danrubel): Remove beginInitializers token from method events
listener.endClassMethod(getOrSet, beforeStart.next, beforeParam.next,
beforeInitializers?.next, token);
break;
case DeclarationKind.Mixin:
listener.endMixinMethod(getOrSet, beforeStart.next, beforeParam.next,
beforeInitializers?.next, token);
break;
case DeclarationKind.Extension:
if (optional(';', bodyStart) && externalToken == null) {
reportRecoverableError(isOperator ? name.next : name,
fasta.messageExtensionDeclaresAbstractMember);
}
listener.endExtensionMethod(getOrSet, beforeStart.next,
beforeParam.next, beforeInitializers?.next, token);
break;
case DeclarationKind.TopLevel:
throw "Internal error: TopLevel method.";
break;
}
}
return token;
}
Token parseFactoryMethod(Token token, DeclarationKind kind, Token beforeStart,
Token externalToken, Token staticOrCovariant, Token varFinalOrConst) {
Token factoryKeyword = token = token.next;
assert(optional('factory', factoryKeyword));
if (!isValidTypeReference(token.next)) {
// Recovery
ModifierRecoveryContext context = new ModifierRecoveryContext(this)
..externalToken = externalToken
..staticOrCovariant = staticOrCovariant
..varFinalOrConst = varFinalOrConst;
token = context.parseModifiersAfterFactory(token);
externalToken = context.externalToken;
staticOrCovariant = context.staticToken ?? context.covariantToken;
varFinalOrConst = context.varFinalOrConst;
context = null;
}
if (staticOrCovariant != null) {
reportRecoverableErrorWithToken(
staticOrCovariant, fasta.templateExtraneousModifier);
}
if (varFinalOrConst != null && !optional('const', varFinalOrConst)) {
reportRecoverableErrorWithToken(
varFinalOrConst, fasta.templateExtraneousModifier);
varFinalOrConst = null;
}
listener.beginFactoryMethod(beforeStart, externalToken, varFinalOrConst);
token = ensureIdentifier(token, IdentifierContext.methodDeclaration);
token = parseQualifiedRestOpt(
token, IdentifierContext.methodDeclarationContinuation);
token = parseMethodTypeVar(token);
token = parseFormalParametersRequiredOpt(token, MemberKind.Factory);
Token asyncToken = token.next;
token = parseAsyncModifierOpt(token);
Token next = token.next;
if (!inPlainSync) {
reportRecoverableError(asyncToken, fasta.messageFactoryNotSync);
}
if (optional('=', next)) {
if (externalToken != null) {
reportRecoverableError(next, fasta.messageExternalFactoryRedirection);
}
token = parseRedirectingFactoryBody(token);
} else if (externalToken != null) {
if (!optional(';', next)) {
reportRecoverableError(next, fasta.messageExternalFactoryWithBody);
}
token = parseFunctionBody(token, false, true);
} else {
if (varFinalOrConst != null && !optional('native', next)) {
if (optional('const', varFinalOrConst)) {
reportRecoverableError(varFinalOrConst, fasta.messageConstFactory);
}
}
token = parseFunctionBody(token, false, false);
}
switch (kind) {
case DeclarationKind.Class:
listener.endClassFactoryMethod(beforeStart.next, factoryKeyword, token);
break;
case DeclarationKind.Mixin:
reportRecoverableError(
factoryKeyword, fasta.messageMixinDeclaresConstructor);
listener.endMixinFactoryMethod(beforeStart.next, factoryKeyword, token);
break;
case DeclarationKind.Extension:
reportRecoverableError(
factoryKeyword, fasta.messageExtensionDeclaresConstructor);
listener.endExtensionFactoryMethod(
beforeStart.next, factoryKeyword, token);
break;
case DeclarationKind.TopLevel:
throw "Internal error: TopLevel factory.";
break;
}
return token;
}
Token parseOperatorName(Token token) {
Token beforeToken = token;
token = token.next;
assert(optional('operator', token));
Token next = token.next;
if (next.isUserDefinableOperator) {
if (computeTypeParamOrArg(token) != noTypeParamOrArg) {
// `operator` is being used as an identifier.
// For example: `int operator<T>(foo) => 0;`
listener.handleIdentifier(token, IdentifierContext.methodDeclaration);
return token;
} else {
listener.handleOperatorName(token, next);
return next;
}
} else if (optional('(', next)) {
return ensureIdentifier(beforeToken, IdentifierContext.operatorName);
} else if (isUnaryMinus(next)) {
// Recovery
reportRecoverableErrorWithToken(next, fasta.templateUnexpectedToken);
next = next.next;
listener.handleOperatorName(token, next);
return next;
} else {
// Recovery
// Scanner reports an error for `===` and `!==`.
if (next.type != TokenType.EQ_EQ_EQ &&
next.type != TokenType.BANG_EQ_EQ) {
// The user has specified an invalid operator name.
// Report the error, accept the invalid operator name, and move on.
reportRecoverableErrorWithToken(next, fasta.templateInvalidOperator);
}
listener.handleInvalidOperatorName(token, next);
return next;
}
}
Token parseFunctionExpression(Token token) {
Token beginToken = token.next;
listener.beginFunctionExpression(beginToken);
token = parseFormalParametersRequiredOpt(token, MemberKind.Local);
token = parseAsyncOptBody(token, true, false);
listener.endFunctionExpression(beginToken, token.next);
return token;
}
Token parseFunctionLiteral(
Token start,
Token beforeName,
Token name,
TypeInfo typeInfo,
TypeParamOrArgInfo typeParam,
IdentifierContext context) {
Token formals = typeParam.parseVariables(name, this);
listener.beginNamedFunctionExpression(start.next);
typeInfo.parseType(start, this);
return parseNamedFunctionRest(beforeName, start.next, formals, true);
}
/// Parses the rest of a named function declaration starting from its [name]
/// but then skips any type parameters and continue parsing from [formals]
/// (the formal parameters).
///
/// If [isFunctionExpression] is true, this method parses the rest of named
/// function expression which isn't legal syntax in Dart. Useful for
/// recovering from Javascript code being pasted into a Dart program, as it
/// will interpret `function foo() {}` as a named function expression with
/// return type `function` and name `foo`.
///
/// Precondition: the parser has previously generated these events:
///
/// - Type variables.
/// - `beginLocalFunctionDeclaration` if [isFunctionExpression] is false,
/// otherwise `beginNamedFunctionExpression`.
/// - Return type.
Token parseNamedFunctionRest(
Token beforeName, Token begin, Token formals, bool isFunctionExpression) {
Token token = beforeName.next;
listener.beginFunctionName(token);
token =
ensureIdentifier(beforeName, IdentifierContext.localFunctionDeclaration)
.next;
if (isFunctionExpression) {
reportRecoverableError(
beforeName.next, fasta.messageNamedFunctionExpression);
}
listener.endFunctionName(begin, token);
token = parseFormalParametersRequiredOpt(formals, MemberKind.Local);
token = parseInitializersOpt(token);
token = parseAsyncOptBody(token, isFunctionExpression, false);
if (isFunctionExpression) {
listener.endNamedFunctionExpression(token);
} else {
listener.endLocalFunctionDeclaration(token);
}
return token;
}
/// Parses a function body optionally preceded by an async modifier (see
/// [parseAsyncModifierOpt]). This method is used in both expression context
/// (when [ofFunctionExpression] is true) and statement context. In statement
/// context (when [ofFunctionExpression] is false), and if the function body
/// is on the form `=> expression`, a trailing semicolon is required.
///
/// It's an error if there's no function body unless [allowAbstract] is true.
Token parseAsyncOptBody(
Token token, bool ofFunctionExpression, bool allowAbstract) {
AsyncModifier savedAsyncModifier = asyncState;
token = parseAsyncModifierOpt(token);
token = parseFunctionBody(token, ofFunctionExpression, allowAbstract);
asyncState = savedAsyncModifier;
return token;
}
Token parseConstructorReference(Token token, [TypeParamOrArgInfo typeArg]) {
Token start =
ensureIdentifier(token, IdentifierContext.constructorReference);
listener.beginConstructorReference(start);
token = parseQualifiedRestOpt(
start, IdentifierContext.constructorReferenceContinuation);
typeArg ??= computeTypeParamOrArg(token);
token = typeArg.parseArguments(token, this);
Token period = null;
if (optional('.', token.next)) {
period = token.next;
token = ensureIdentifier(period,
IdentifierContext.constructorReferenceContinuationAfterTypeArguments);
} else {
listener.handleNoConstructorReferenceContinuationAfterTypeArguments(
token.next);
}
listener.endConstructorReference(start, period, token.next);
return token;
}
Token parseRedirectingFactoryBody(Token token) {
token = token.next;
assert(optional('=', token));
listener.beginRedirectingFactoryBody(token);
Token equals = token;
token = parseConstructorReference(token);
token = ensureSemicolon(token);
listener.endRedirectingFactoryBody(equals, token);
return token;
}
Token skipFunctionBody(Token token, bool isExpression, bool allowAbstract) {
assert(!isExpression);
token = skipAsyncModifier(token);
Token next = token.next;
if (optional('native', next)) {
Token nativeToken = next;
// TODO(danrubel): skip the native clause rather than parsing it
// or remove this code completely when we remove support
// for the `native` clause.
token = parseNativeClause(token);
next = token.next;
if (optional(';', next)) {
listener.handleNativeFunctionBodySkipped(nativeToken, next);
return token.next;
}
listener.handleNativeFunctionBodyIgnored(nativeToken, next);
// Fall through to recover and skip function body
}
String value = next.stringValue;
if (identical(value, ';')) {
token = next;
if (!allowAbstract) {
reportRecoverableError(token, fasta.messageExpectedBody);
}
listener.handleNoFunctionBody(token);
} else if (identical(value, '=>')) {
token = parseExpression(next);
// There ought to be a semicolon following the expression, but we check
// before advancing in order to be consistent with the way the method
// [parseFunctionBody] recovers when the semicolon is missing.
if (optional(';', token.next)) {
token = token.next;
}
listener.handleFunctionBodySkipped(token, true);
} else if (identical(value, '=')) {
token = next;
reportRecoverableError(token, fasta.messageExpectedBody);
token = parseExpression(token);
// There ought to be a semicolon following the expression, but we check
// before advancing in order to be consistent with the way the method
// [parseFunctionBody] recovers when the semicolon is missing.
if (optional(';', token.next)) {
token = token.next;
}
listener.handleFunctionBodySkipped(token, true);
} else {
token = skipBlock(token);
listener.handleFunctionBodySkipped(token, false);
}
return token;
}
/// Parses a function body. This method is used in both expression context
/// (when [ofFunctionExpression] is true) and statement context. In statement
/// context (when [ofFunctionExpression] is false), and if the function body
/// is on the form `=> expression`, a trailing semicolon is required.
///
/// It's an error if there's no function body unless [allowAbstract] is true.
Token parseFunctionBody(
Token token, bool ofFunctionExpression, bool allowAbstract) {
Token next = token.next;
if (optional('native', next)) {
Token nativeToken = next;
token = parseNativeClause(token);
next = token.next;
if (optional(';', next)) {
listener.handleNativeFunctionBody(nativeToken, next);
return next;
}
reportRecoverableError(next, fasta.messageExternalMethodWithBody);
listener.handleNativeFunctionBodyIgnored(nativeToken, next);
// Ignore the native keyword and fall through to parse the body
}
if (optional(';', next)) {
if (!allowAbstract) {
reportRecoverableError(next, fasta.messageExpectedBody);
}
listener.handleEmptyFunctionBody(next);
return next;
} else if (optional('=>', next)) {
return parseExpressionFunctionBody(next, ofFunctionExpression);
} else if (optional('=', next)) {
// Recover from a bad factory method.
reportRecoverableError(next, fasta.messageExpectedBody);
next = rewriter.insertToken(
next, new SyntheticToken(TokenType.FUNCTION, next.next.charOffset));
Token begin = next;
token = parseExpression(next);
if (!ofFunctionExpression) {
token = ensureSemicolon(token);
listener.handleExpressionFunctionBody(begin, token);
} else {
listener.handleExpressionFunctionBody(begin, null);
}
return token;
}
Token begin = next;
int statementCount = 0;
if (!optional('{', next)) {
// Recovery
// If `return` used instead of `=>`, then report an error and continue
if (optional('return', next)) {
reportRecoverableError(next, fasta.messageExpectedBody);
next = rewriter.insertToken(
next, new SyntheticToken(TokenType.FUNCTION, next.next.charOffset));
return parseExpressionFunctionBody(next, ofFunctionExpression);
}
// If there is a stray simple identifier in the function expression
// because the user is typing (e.g. `() asy => null;`)
// then report an error, skip the token, and continue parsing.
if (next.isKeywordOrIdentifier && optional('=>', next.next)) {
reportRecoverableErrorWithToken(next, fasta.templateUnexpectedToken);
return parseExpressionFunctionBody(next.next, ofFunctionExpression);
}
if (next.isKeywordOrIdentifier && optional('{', next.next)) {
reportRecoverableErrorWithToken(next, fasta.templateUnexpectedToken);
token = next;
begin = next = token.next;
// Fall through to parse the block.
} else {
token = ensureBlock(token, fasta.templateExpectedFunctionBody, null);
listener.handleInvalidFunctionBody(token);
return token.endGroup;
}
}
LoopState savedLoopState = loopState;
loopState = LoopState.OutsideLoop;
listener.beginBlockFunctionBody(begin);
token = next;
while (notEofOrValue('}', token.next)) {
Token startToken = token.next;
token = parseStatement(token);
if (identical(token.next, startToken)) {
// No progress was made, so we report the current token as being invalid
// and move forward.
reportRecoverableError(
token, fasta.templateUnexpectedToken.withArguments(token));
token = token.next;
}
++statementCount;
}
token = token.next;
assert(token.isEof || optional('}', token));
listener.endBlockFunctionBody(statementCount, begin, token);
loopState = savedLoopState;
return token;
}
Token parseExpressionFunctionBody(Token token, bool ofFunctionExpression) {
assert(optional('=>', token));
Token begin = token;
token = parseExpression(token);
if (!ofFunctionExpression) {
token = ensureSemicolon(token);
listener.handleExpressionFunctionBody(begin, token);
} else {
listener.handleExpressionFunctionBody(begin, null);
}
if (inGenerator) {
listener.handleInvalidStatement(
begin, fasta.messageGeneratorReturnsValue);
}
return token;
}
Token skipAsyncModifier(Token token) {
String value = token.next.stringValue;
if (identical(value, 'async')) {
token = token.next;
value = token.next.stringValue;
if (identical(value, '*')) {
token = token.next;
}
} else if (identical(value, 'sync')) {
token = token.next;
value = token.next.stringValue;
if (identical(value, '*')) {
token = token.next;
}
}
return token;
}
Token parseAsyncModifierOpt(Token token) {
Token async;
Token star;
asyncState = AsyncModifier.Sync;
Token next = token.next;
if (optional('async', next)) {
async = token = next;
next = token.next;
if (optional('*', next)) {
asyncState = AsyncModifier.AsyncStar;
star = next;
token = next;
} else {
asyncState = AsyncModifier.Async;
}
} else if (optional('sync', next)) {
async = token = next;
next = token.next;
if (optional('*', next)) {
asyncState = AsyncModifier.SyncStar;
star = next;
token = next;
} else {
reportRecoverableError(async, fasta.messageInvalidSyncModifier);
}
}
listener.handleAsyncModifier(async, star);
if (!inPlainSync && optional(';', token.next)) {
reportRecoverableError(token.next, fasta.messageAbstractNotSync);
}
return token;
}
int statementDepth = 0;
Token parseStatement(Token token) {
if (statementDepth++ > 500) {
// This happens for degenerate programs, for example, a lot of nested
// if-statements. The language test deep_nesting2_negative_test, for
// example, provokes this.
return recoverFromStackOverflow(token);
}
Token result = parseStatementX(token);
statementDepth--;
return result;
}
Token parseStatementX(Token token) {
if (identical(token.next.kind, IDENTIFIER_TOKEN)) {
if (optional(':', token.next.next)) {
return parseLabeledStatement(token);
}
return parseExpressionStatementOrDeclarationAfterModifiers(
token, token, null, null, null, false);
}
final String value = token.next.stringValue;
if (identical(value, '{')) {
// The scanner ensures that `{` always has a closing `}`.
return parseBlock(token, BlockKind.statement);
} else if (identical(value, 'return')) {
return parseReturnStatement(token);
} else if (identical(value, 'var') || identical(value, 'final')) {
Token varOrFinal = token.next;
if (!isModifier(varOrFinal.next)) {
return parseExpressionStatementOrDeclarationAfterModifiers(
varOrFinal, token, null, varOrFinal, null, false);
}
return parseExpressionStatementOrDeclaration(token);
} else if (identical(value, 'if')) {
return parseIfStatement(token);
} else if (identical(value, 'await') && optional('for', token.next.next)) {
return parseForStatement(token.next, token.next);
} else if (identical(value, 'for')) {
return parseForStatement(token, null);
} else if (identical(value, 'rethrow')) {
return parseRethrowStatement(token);
} else if (identical(value, 'while')) {
return parseWhileStatement(token);
} else if (identical(value, 'do')) {
return parseDoWhileStatement(token);
} else if (identical(value, 'try')) {
return parseTryStatement(token);
} else if (identical(value, 'switch')) {
return parseSwitchStatement(token);
} else if (identical(value, 'break')) {
return parseBreakStatement(token);
} else if (identical(value, 'continue')) {
return parseContinueStatement(token);
} else if (identical(value, 'assert')) {
return parseAssertStatement(token);
} else if (identical(value, ';')) {
return parseEmptyStatement(token);
} else if (identical(value, 'yield')) {
switch (asyncState) {
case AsyncModifier.Sync:
if (optional(':', token.next.next)) {
return parseLabeledStatement(token);
}
return parseExpressionStatementOrDeclaration(token);
case AsyncModifier.SyncStar:
case AsyncModifier.AsyncStar:
return parseYieldStatement(token);
case AsyncModifier.Async:
reportRecoverableError(token.next, fasta.messageYieldNotGenerator);
return parseYieldStatement(token);
}
throw "Internal error: Unknown asyncState: '$asyncState'.";
} else if (identical(value, 'const')) {
return parseExpressionStatementOrConstDeclaration(token);
} else if (identical(value, 'await')) {
if (inPlainSync) {
if (!looksLikeAwaitExpression(token)) {
return parseExpressionStatementOrDeclaration(token);
}
// Recovery: looks like an expression preceded by `await`
// but not inside an async context.
// Fall through to parseExpressionStatement
// and parseAwaitExpression will report the error.
}
return parseExpressionStatement(token);
} else if (identical(value, 'set') && token.next.next.isIdentifier) {
// Recovery: invalid use of `set`
reportRecoverableErrorWithToken(
token.next, fasta.templateUnexpectedToken);
return parseStatementX(token.next);
} else if (token.next.isIdentifier) {
if (optional(':', token.next.next)) {
return parseLabeledStatement(token);
}
return parseExpressionStatementOrDeclaration(token);
} else {
return parseExpressionStatementOrDeclaration(token);
}
}
/// ```
/// yieldStatement:
/// 'yield' expression? ';'
/// ;
/// ```
Token parseYieldStatement(Token token) {
Token begin = token = token.next;
assert(optional('yield', token));
listener.beginYieldStatement(begin);
Token starToken;
if (optional('*', token.next)) {
starToken = token = token.next;
}
token = parseExpression(token);
token = ensureSemicolon(token);
listener.endYieldStatement(begin, starToken, token);
return token;
}
/// ```
/// returnStatement:
/// 'return' expression? ';'
/// ;
/// ```
Token parseReturnStatement(Token token) {
Token begin = token = token.next;
assert(optional('return', token));
listener.beginReturnStatement(begin);
Token next = token.next;
if (optional(';', next)) {
listener.endReturnStatement(false, begin, next);
return next;
}
token = parseExpression(token);
token = ensureSemicolon(token);
listener.endReturnStatement(true, begin, token);
if (inGenerator) {
listener.handleInvalidStatement(
begin, fasta.messageGeneratorReturnsValue);
}
return token;
}
/// ```
/// label:
/// identifier ':'
/// ;
/// ```
Token parseLabel(Token token) {
assert(token.next.isIdentifier);
token = ensureIdentifier(token, IdentifierContext.labelDeclaration).next;
assert(optional(':', token));
listener.handleLabel(token);
return token;
}
/// ```
/// statement:
/// label* nonLabelledStatement
/// ;
/// ```
Token parseLabeledStatement(Token token) {
Token next = token.next;
assert(next.isIdentifier);
assert(optional(':', next.next));
int labelCount = 0;
do {
token = parseLabel(token);
next = token.next;
labelCount++;
} while (next.isIdentifier && optional(':', next.next));
listener.beginLabeledStatement(next, labelCount);
token = parseStatement(token);
listener.endLabeledStatement(labelCount);
return token;
}
/// ```
/// expressionStatement:
/// expression? ';'
/// ;
/// ```
///
/// Note: This method can fail to make progress. If there is neither an
/// expression nor a semi-colon, then a synthetic identifier and synthetic
/// semicolon will be inserted before [token] and the semicolon will be
/// returned.
Token parseExpressionStatement(Token token) {
// TODO(brianwilkerson): If the next token is not the start of a valid
// expression, then this method shouldn't report that we have an expression
// statement.
token = parseExpression(token);
token = ensureSemicolon(token);
listener.handleExpressionStatement(token);
return token;
}
int expressionDepth = 0;
Token parseExpression(Token token) {
if (expressionDepth++ > 500) {
// This happens in degenerate programs, for example, with a lot of nested
// list literals. This is provoked by, for example, the language test
// deep_nesting1_negative_test.
Token next = token.next;
reportRecoverableError(next, fasta.messageStackOverflow);
// Recovery
Token endGroup = next.endGroup;
if (endGroup != null) {
while (!next.isEof && !identical(next, endGroup)) {
token = next;
next = token.next;
}
} else {
while (!isOneOf(next, const [')', ']', '}', ';'])) {
token = next;
next = token.next;
}
}
if (!token.isEof) {
token = rewriter.insertSyntheticIdentifier(token);
listener.handleIdentifier(token, IdentifierContext.expression);
}
} else {
token = optional('throw', token.next)
? parseThrowExpression(token, true)
: parsePrecedenceExpression(token, ASSIGNMENT_PRECEDENCE, true);
}
expressionDepth--;
return token;
}
Token parseExpressionWithoutCascade(Token token) {
return optional('throw', token.next)
? parseThrowExpression(token, false)
: parsePrecedenceExpression(token, ASSIGNMENT_PRECEDENCE, false);
}
Token parseConditionalExpressionRest(Token token) {
Token question = token = token.next;
assert(optional('?', question));
listener.beginConditionalExpression(token);
token = parseExpressionWithoutCascade(token);
Token colon = ensureColon(token);
listener.handleConditionalExpressionColon();
token = parseExpressionWithoutCascade(colon);
listener.endConditionalExpression(question, colon);
return token;
}
Token parsePrecedenceExpression(
Token token, int precedence, bool allowCascades) {
assert(precedence >= 1);
assert(precedence <= SELECTOR_PRECEDENCE);
token = parseUnaryExpression(token, allowCascades);
TypeParamOrArgInfo typeArg = computeMethodTypeArguments(token);
if (typeArg != noTypeParamOrArg) {
// For example a(b)<T>(c), where token is before '<'.
token = typeArg.parseArguments(token, this);
assert(optional('(', token.next));
}
Token next = token.next;
TokenType type = next.type;
int tokenLevel = _computePrecedence(next);
for (int level = tokenLevel; level >= precedence; --level) {
int lastBinaryExpressionLevel = -1;
Token lastCascade;
while (identical(tokenLevel, level)) {
Token operator = next;
if (identical(tokenLevel, CASCADE_PRECEDENCE)) {
if (!allowCascades) {
return token;
} else if (lastCascade != null && optional('?..', next)) {
reportRecoverableError(
next, fasta.messageNullAwareCascadeOutOfOrder);
}
lastCascade = next;
token = parseCascadeExpression(token);
} else if (identical(tokenLevel, ASSIGNMENT_PRECEDENCE)) {
// Right associative, so we recurse at the same precedence
// level.
Token next = token.next;
token = optional('throw', next.next)
? parseThrowExpression(next, false)
: parsePrecedenceExpression(next, level, allowCascades);
listener.handleAssignmentExpression(operator);
} else if (identical(tokenLevel, POSTFIX_PRECEDENCE)) {
if ((identical(type, TokenType.PLUS_PLUS)) ||
(identical(type, TokenType.MINUS_MINUS))) {
listener.handleUnaryPostfixAssignmentExpression(token.next);
token = next;
} else if (identical(type, TokenType.BANG)) {
listener.handleNonNullAssertExpression(next);
token = next;
}
} else if (identical(tokenLevel, SELECTOR_PRECEDENCE)) {
if (identical(type, TokenType.PERIOD) ||
identical(type, TokenType.QUESTION_PERIOD)) {
// Left associative, so we recurse at the next higher precedence
// level. However, SELECTOR_PRECEDENCE is the highest level, so we
// should just call [parseUnaryExpression] directly. However, a
// unary expression isn't legal after a period, so we call
// [parsePrimary] instead.
token = parsePrimary(
token.next, IdentifierContext.expressionContinuation);
listener.endBinaryExpression(operator);
} else if (identical(type, TokenType.OPEN_PAREN) ||
identical(type, TokenType.OPEN_SQUARE_BRACKET) ||
identical(type, TokenType.QUESTION_PERIOD_OPEN_SQUARE_BRACKET)) {
token = parseArgumentOrIndexStar(token, typeArg);
} else if (identical(type, TokenType.INDEX)) {
BeginToken replacement = link(
new BeginToken(TokenType.OPEN_SQUARE_BRACKET, next.charOffset,
next.precedingComments),
new Token(TokenType.CLOSE_SQUARE_BRACKET, next.charOffset + 1));
rewriter.replaceTokenFollowing(token, replacement);
replacement.endToken = replacement.next;
token = parseArgumentOrIndexStar(token, noTypeParamOrArg);
} else if (identical(type, TokenType.BANG)) {
listener.handleNonNullAssertExpression(token.next);
token = next;
} else {
// Recovery
reportRecoverableErrorWithToken(
token.next, fasta.templateUnexpectedToken);
token = next;
}
} else if (identical(type, TokenType.IS)) {
token = parseIsOperatorRest(token);
} else if (identical(type, TokenType.AS)) {
token = parseAsOperatorRest(token);
} else if (identical(type, TokenType.QUESTION)) {
token = parseConditionalExpressionRest(token);
} else {
if (level == EQUALITY_PRECEDENCE || level == RELATIONAL_PRECEDENCE) {
// We don't allow (a == b == c) or (a < b < c).
if (lastBinaryExpressionLevel == level) {
// Report an error, then continue parsing as if it is legal.
reportRecoverableError(
next, fasta.messageEqualityCannotBeEqualityOperand);
} else {
// Set a flag to catch subsequent binary expressions of this type.
lastBinaryExpressionLevel = level;
}
}
listener.beginBinaryExpression(next);
// Left associative, so we recurse at the next higher
// precedence level.
token =
parsePrecedenceExpression(token.next, level + 1, allowCascades);
listener.endBinaryExpression(operator);
}
next = token.next;
type = next.type;
tokenLevel = _computePrecedence(next);
}
}
return token;
}
int _computePrecedence(Token token) {
TokenType type = token.type;
if (identical(type, TokenType.BANG)) {
// The '!' has prefix precedence but here it's being used as a
// postfix operator to assert the expression has a non-null value.
TokenType nextType = token.next.type;
if (identical(nextType, TokenType.PERIOD) ||
identical(nextType, TokenType.OPEN_PAREN) ||
identical(nextType, TokenType.OPEN_SQUARE_BRACKET)) {
return SELECTOR_PRECEDENCE;
}
return POSTFIX_PRECEDENCE;
}
return type.precedence;
}
Token parseCascadeExpression(Token token) {
Token cascadeOperator = token = token.next;
assert(optional('..', cascadeOperator) || optional('?..', cascadeOperator));
listener.beginCascade(cascadeOperator);
if (optional('[', token.next)) {
token = parseArgumentOrIndexStar(token, noTypeParamOrArg);
} else {
token = parseSend(token, IdentifierContext.expressionContinuation);
listener.endBinaryExpression(cascadeOperator);
}
Token next = token.next;
Token mark;
do {
mark = token;
if (optional('.', next) || optional('?.', next)) {
Token period = next;
token = parseSend(next, IdentifierContext.expressionContinuation);
next = token.next;
listener.endBinaryExpression(period);
}
TypeParamOrArgInfo typeArg = computeMethodTypeArguments(token);
if (typeArg != noTypeParamOrArg) {
// For example a(b)..<T>(c), where token is '<'.
token = typeArg.parseArguments(token, this);
next = token.next;
assert(optional('(', next));
}
token = parseArgumentOrIndexStar(token, typeArg);
next = token.next;
} while (!identical(mark, token));
if (identical(next.type.precedence, ASSIGNMENT_PRECEDENCE)) {
Token assignment = next;
token = parseExpressionWithoutCascade(next);
listener.handleAssignmentExpression(assignment);
}
listener.endCascade();
return token;
}
Token parseUnaryExpression(Token token, bool allowCascades) {
String value = token.next.stringValue;
// Prefix:
if (identical(value, 'await')) {
if (inPlainSync) {
if (!looksLikeAwaitExpression(token)) {
return parsePrimary(token, IdentifierContext.expression);
}
// Recovery: Looks like an expression preceded by `await`.
// Fall through and let parseAwaitExpression report the error.
}
return parseAwaitExpression(token, allowCascades);
} else if (identical(value, '+')) {
// Dart no longer allows prefix-plus.
rewriteAndRecover(
token,
// TODO(danrubel): Consider reporting "missing identifier" instead.
fasta.messageUnsupportedPrefixPlus,
new SyntheticStringToken(
TokenType.IDENTIFIER, '', token.next.offset));
return parsePrimary(token, IdentifierContext.expression);
} else if ((identical(value, '!')) ||
(identical(value, '-')) ||
(identical(value, '~'))) {
Token operator = token.next;
// Right associative, so we recurse at the same precedence
// level.
token = parsePrecedenceExpression(
token.next, POSTFIX_PRECEDENCE, allowCascades);
listener.handleUnaryPrefixExpression(operator);
return token;
} else if ((identical(value, '++')) || identical(value, '--')) {
// TODO(ahe): Validate this is used correctly.
Token operator = token.next;
// Right associative, so we recurse at the same precedence
// level.
token = parsePrecedenceExpression(
token.next, POSTFIX_PRECEDENCE, allowCascades);
listener.handleUnaryPrefixAssignmentExpression(operator);
return token;
} else if (token.next.isIdentifier) {
Token identifier = token.next;
if (optional(".", identifier.next)) {
identifier = identifier.next.next;
}
if (identifier.isIdentifier) {
// Looking at `identifier ('.' identifier)?`.
if (optional("<", identifier.next)) {
TypeParamOrArgInfo typeArg = computeTypeParamOrArg(identifier);
if (typeArg != noTypeParamOrArg) {
Token endTypeArguments = typeArg.skip(identifier);
if (optional(".", endTypeArguments.next)) {
return parseImplicitCreationExpression(token, typeArg);
}
}
}
}
}
return parsePrimary(token, IdentifierContext.expression);
}
Token parseArgumentOrIndexStar(Token token, TypeParamOrArgInfo typeArg) {
Token next = token.next;
Token beginToken = next;
while (true) {
if (optional('[', next) || optional('?.[', next)) {
assert(typeArg == noTypeParamOrArg);
Token openSquareBracket = next;
bool old = mayParseFunctionExpressions;
mayParseFunctionExpressions = true;
token = parseExpression(next);
next = token.next;
mayParseFunctionExpressions = old;
if (!optional(']', next)) {
// Recovery
reportRecoverableError(
next, fasta.templateExpectedButGot.withArguments(']'));
// Scanner ensures a closing ']'
Token endGroup = openSquareBracket.endGroup;
if (endGroup.isSynthetic) {
// Scanner inserted closing ']' in the wrong place, so move it.
next = rewriter.moveSynthetic(token, endGroup);
} else {
// Skip over unexpected tokens to where the user placed the `]`.
next = endGroup;
}
}
listener.handleIndexedExpression(openSquareBracket, next);
token = next;
typeArg = computeMethodTypeArguments(token);
if (typeArg != noTypeParamOrArg) {
// For example a[b]<T>(c), where token is before '<'.
token = typeArg.parseArguments(token, this);
assert(optional('(', token.next));
}
next = token.next;
} else if (optional('(', next)) {
if (typeArg == noTypeParamOrArg) {
listener.handleNoTypeArguments(next);
}
token = parseArguments(token);
listener.handleSend(beginToken, token);
typeArg = computeMethodTypeArguments(token);
if (typeArg != noTypeParamOrArg) {
// For example a(b)<T>(c), where token is before '<'.
token = typeArg.parseArguments(token, this);
assert(optional('(', token.next));
}
next = token.next;
} else {
break;
}
}
return token;
}
Token parsePrimary(Token token, IdentifierContext context) {
final int kind = token.next.kind;
if (kind == IDENTIFIER_TOKEN) {
return parseSendOrFunctionLiteral(token, context);
} else if (kind == INT_TOKEN || kind == HEXADECIMAL_TOKEN) {
return parseLiteralInt(token);
} else if (kind == DOUBLE_TOKEN) {
return parseLiteralDouble(token);
} else if (kind == STRING_TOKEN) {
return parseLiteralString(token);
} else if (kind == HASH_TOKEN) {
return parseLiteralSymbol(token);
} else if (kind == KEYWORD_TOKEN) {
final String value = token.next.stringValue;
if (identical(value, "true") || identical(value, "false")) {
return parseLiteralBool(token);
} else if (identical(value, "null")) {
return parseLiteralNull(token);
} else if (identical(value, "this")) {
return parseThisExpression(token, context);
} else if (identical(value, "super")) {
return parseSuperExpression(token, context);
} else if (identical(value, "new")) {
return parseNewExpression(token);
} else if (identical(value, "const")) {
return parseConstExpression(token);
} else if (identical(value, "void")) {
return parseSendOrFunctionLiteral(token, context);
} else if (!inPlainSync &&
(identical(value, "yield") || identical(value, "async"))) {
// Fall through to the recovery code.
} else if (identical(value, "assert")) {
return parseAssert(token, Assert.Expression);
} else if (token.next.isIdentifier) {
return parseSendOrFunctionLiteral(token, context);
} else if (identical(value, "return")) {
// Recovery
token = token.next;
reportRecoverableErrorWithToken(token, fasta.templateUnexpectedToken);
return parsePrimary(token, context);
} else {
// Fall through to the recovery code.
}
} else if (kind == OPEN_PAREN_TOKEN) {
return parseParenthesizedExpressionOrFunctionLiteral(token);
} else if (kind == OPEN_SQUARE_BRACKET_TOKEN ||
optional('[]', token.next)) {
listener.handleNoTypeArguments(token.next);
return parseLiteralListSuffix(token, null);
} else if (kind == OPEN_CURLY_BRACKET_TOKEN) {
listener.handleNoTypeArguments(token.next);
return parseLiteralSetOrMapSuffix(token, null);
} else if (kind == LT_TOKEN) {
return parseLiteralListSetMapOrFunction(token, null);
} else {
// Fall through to the recovery code.
}
//
// Recovery code.
//
return parseSend(token, context);
}
Token parseParenthesizedExpressionOrFunctionLiteral(Token token) {
Token next = token.next;
assert(optional('(', next));
Token nextToken = next.endGroup.next;
int kind = nextToken.kind;
if (mayParseFunctionExpressions) {
if ((identical(kind, FUNCTION_TOKEN) ||
identical(kind, OPEN_CURLY_BRACKET_TOKEN))) {
listener.handleNoTypeVariables(next);
return parseFunctionExpression(token);
} else if (identical(kind, KEYWORD_TOKEN) ||
identical(kind, IDENTIFIER_TOKEN)) {
if (optional('async', nextToken) || optional('sync', nextToken)) {
listener.handleNoTypeVariables(next);
return parseFunctionExpression(token);
}
// Recovery
// If there is a stray simple identifier in the function expression
// because the user is typing (e.g. `() asy {}`) then continue parsing
// and allow parseFunctionExpression to report an unexpected token.
kind = nextToken.next.kind;
if ((identical(kind, FUNCTION_TOKEN) ||
identical(kind, OPEN_CURLY_BRACKET_TOKEN))) {
listener.handleNoTypeVariables(next);
return parseFunctionExpression(token);
}
}
}
bool old = mayParseFunctionExpressions;
mayParseFunctionExpressions = true;
token = parseParenthesizedExpression(token);
mayParseFunctionExpressions = old;
return token;
}
Token ensureParenthesizedCondition(Token token) {
Token openParen = token.next;
if (!optional('(', openParen)) {
// Recover
reportRecoverableError(
openParen, fasta.templateExpectedToken.withArguments('('));
openParen = rewriter.insertParens(token, false);
}
token = parseExpressionInParenthesisRest(openParen);
listener.handleParenthesizedCondition(openParen);
return token;
}
Token parseParenthesizedExpression(Token token) {
Token begin = token.next;
token = parseExpressionInParenthesis(token);
listener.handleParenthesizedExpression(begin);
return token;
}
Token parseExpressionInParenthesis(Token token) {
return parseExpressionInParenthesisRest(token.next);
}
Token parseExpressionInParenthesisRest(Token token) {
assert(optional('(', token));
BeginToken begin = token;
token = parseExpression(token);
token = ensureCloseParen(token, begin);
assert(optional(')', token));
return token;
}
Token parseThisExpression(Token token, IdentifierContext context) {
Token thisToken = token = token.next;
assert(optional('this', thisToken));
listener.handleThisExpression(thisToken, context);
Token next = token.next;
if (optional('(', next)) {
// Constructor forwarding.
listener.handleNoTypeArguments(next);
token = parseArguments(token);
listener.handleSend(thisToken, token.next);
}
return token;
}
Token parseSuperExpression(Token token, IdentifierContext context) {
Token superToken = token = token.next;
assert(optional('super', token));
listener.handleSuperExpression(superToken, context);
Token next = token.next;
if (optional('(', next)) {
// Super constructor.
listener.handleNoTypeArguments(next);
token = parseArguments(token);
listener.handleSend(superToken, token.next);
} else if (optional("?.", next)) {
reportRecoverableError(next, fasta.messageSuperNullAware);
}
return token;
}
/// This method parses the portion of a list literal starting with the left
/// square bracket.
///
/// ```
/// listLiteral:
/// 'const'? typeArguments? '[' (expressionList ','?)? ']'
/// ;
/// ```
///
/// Provide a [constKeyword] if the literal is preceded by 'const', or `null`
/// if not. This is a suffix parser because it is assumed that type arguments
/// have been parsed, or `listener.handleNoTypeArguments` has been executed.
Token parseLiteralListSuffix(Token token, Token constKeyword) {
Token beforeToken = token;
Token beginToken = token = token.next;
assert(optional('[', token) || optional('[]', token));
int count = 0;
if (optional('[]', token)) {
token = rewriteSquareBrackets(beforeToken).next;
listener.handleLiteralList(0, token, constKeyword, token.next);
return token.next;
}
bool old = mayParseFunctionExpressions;
mayParseFunctionExpressions = true;
while (true) {
Token next = token.next;
if (optional(']', next)) {
token = next;
break;
}
int ifCount = 0;
LiteralEntryInfo info = computeLiteralEntry(token);
while (info != null) {
if (info.hasEntry) {
token = parseExpression(token);
} else {
token = info.parse(token, this);
}
ifCount += info.ifConditionDelta;
info = info.computeNext(token);
}
next = token.next;
++count;
if (!optional(',', next)) {
if (optional(']', next)) {
token = next;
break;
}
// Recovery
if (!looksLikeLiteralEntry(next)) {
if (beginToken.endGroup.isSynthetic) {
// The scanner has already reported an error,
// but inserted `]` in the wrong place.
token = rewriter.moveSynthetic(token, beginToken.endGroup);
} else {
// Report an error and jump to the end of the list.
reportRecoverableError(
next, fasta.templateExpectedButGot.withArguments(']'));
token = beginToken.endGroup;
}
break;
}
// This looks like the start of an expression.
// Report an error, insert the comma, and continue parsing.
SyntheticToken comma = new SyntheticToken(TokenType.COMMA, next.offset);
Message message = ifCount > 0
? fasta.messageExpectedElseOrComma
: fasta.templateExpectedButGot.withArguments(',');
next = rewriteAndRecover(token, message, comma);
}
token = next;
}
mayParseFunctionExpressions = old;
listener.handleLiteralList(count, beginToken, constKeyword, token);
return token;
}
/// This method parses the portion of a set or map literal that starts with
/// the left curly brace when there are no leading type arguments.
Token parseLiteralSetOrMapSuffix(Token token, Token constKeyword) {
Token leftBrace = token = token.next;
assert(optional('{', leftBrace));
Token next = token.next;
if (optional('}', next)) {
listener.handleLiteralSetOrMap(0, leftBrace, constKeyword, next, false);
return next;
}
final bool old = mayParseFunctionExpressions;
mayParseFunctionExpressions = true;
int count = 0;
// TODO(danrubel): hasSetEntry parameter exists for replicating existing
// behavior and will be removed once unified collection has been enabled
bool hasSetEntry;
while (true) {
int ifCount = 0;
LiteralEntryInfo info = computeLiteralEntry(token);
if (info == simpleEntry) {
// TODO(danrubel): Remove this section and use the while loop below
// once hasSetEntry is no longer needed.
token = parseExpression(token);
bool isMapEntry = optional(':', token.next);
hasSetEntry ??= !isMapEntry;
if (isMapEntry) {
Token colon = token.next;
token = parseExpression(colon);
listener.handleLiteralMapEntry(colon, token.next);
}
} else {
while (info != null) {
if (info.hasEntry) {
token = parseExpression(token);
if (optional(':', token.next)) {
Token colon = token.next;
token = parseExpression(colon);
listener.handleLiteralMapEntry(colon, token.next);
}
} else {
token = info.parse(token, this);
}
ifCount += info.ifConditionDelta;
info = info.computeNext(token);
}
}
++count;
next = token.next;
Token comma;
if (optional(',', next)) {
comma = token = next;
next = token.next;
}
if (optional('}', next)) {
listener.handleLiteralSetOrMap(
count, leftBrace, constKeyword, next, hasSetEntry ?? false);
mayParseFunctionExpressions = old;
return next;
}
if (comma == null) {
// Recovery
if (looksLikeLiteralEntry(next)) {
// If this looks like the start of an expression,
// then report an error, insert the comma, and continue parsing.
// TODO(danrubel): Consider better error message
SyntheticToken comma =
new SyntheticToken(TokenType.COMMA, next.offset);
Message message = ifCount > 0
? fasta.messageExpectedElseOrComma
: fasta.templateExpectedButGot.withArguments(',');
token = rewriteAndRecover(token, message, comma);
} else {
reportRecoverableError(
next, fasta.templateExpectedButGot.withArguments('}'));
// Scanner guarantees a closing curly bracket
next = leftBrace.endGroup;
listener.handleLiteralSetOrMap(
count, leftBrace, constKeyword, next, hasSetEntry ?? false);
mayParseFunctionExpressions = old;
return next;
}
}
}
}
/// formalParameterList functionBody.
///
/// This is a suffix parser because it is assumed that type arguments have
/// been parsed, or `listener.handleNoTypeArguments(..)` has been executed.
Token parseLiteralFunctionSuffix(Token token) {
assert(optional('(', token.next));
// Scanner ensures `(` has matching `)`.
Token next = token.next.endGroup.next;
int kind = next.kind;
if (!identical(kind, FUNCTION_TOKEN) &&
!identical(kind, OPEN_CURLY_BRACKET_TOKEN) &&
(!identical(kind, KEYWORD_TOKEN) ||
!optional('async', next) && !optional('sync', next))) {
reportRecoverableErrorWithToken(next, fasta.templateUnexpectedToken);
}
return parseFunctionExpression(token);
}
/// genericListLiteral | genericMapLiteral | genericFunctionLiteral.
///
/// Where
/// genericListLiteral ::= typeArguments '[' (expressionList ','?)? ']'
/// genericMapLiteral ::=
/// typeArguments '{' (mapLiteralEntry (',' mapLiteralEntry)* ','?)? '}'
/// genericFunctionLiteral ::=
/// typeParameters formalParameterList functionBody
/// Provide token for [constKeyword] if preceded by 'const', null if not.
Token parseLiteralListSetMapOrFunction(
final Token start, Token constKeyword) {
assert(optional('<', start.next));
TypeParamOrArgInfo typeParamOrArg = computeTypeParamOrArg(start, true);
Token token = typeParamOrArg.skip(start);
if (optional('(', token.next)) {
if (constKeyword != null) {
reportRecoverableErrorWithToken(
constKeyword, fasta.templateUnexpectedToken);
}
token = typeParamOrArg.parseVariables(start, this);
return parseLiteralFunctionSuffix(token);
}
// Note that parseArguments can rewrite the token stream!
token = typeParamOrArg.parseArguments(start, this);
Token next = token.next;
if (optional('{', next)) {
if (typeParamOrArg.typeArgumentCount > 2) {
listener.handleRecoverableError(
fasta.messageSetOrMapLiteralTooManyTypeArguments,
start.next,
token);
}
return parseLiteralSetOrMapSuffix(token, constKeyword);
}
if (!optional('[', next) && !optional('[]', next)) {
// TODO(danrubel): Improve this error message.
reportRecoverableError(
next, fasta.templateExpectedButGot.withArguments('['));
rewriter.insertSyntheticToken(token, TokenType.INDEX);
}
return parseLiteralListSuffix(token, constKeyword);
}
/// ```
/// mapLiteralEntry:
/// expression ':' expression |
/// 'if' '(' expression ')' mapLiteralEntry ( 'else' mapLiteralEntry )? |
/// 'await'? 'for' '(' forLoopParts ')' mapLiteralEntry |
/// ( '...' | '...?' ) expression
/// ;
/// ```
Token parseMapLiteralEntry(Token token) {
// Assume the listener rejects non-string keys.
// TODO(brianwilkerson): Change the assumption above by moving error
// checking into the parser, making it possible to recover.
LiteralEntryInfo info = computeLiteralEntry(token);
while (info != null) {
if (info.hasEntry) {
token = parseExpression(token);
Token colon = ensureColon(token);
token = parseExpression(colon);
// TODO remove unused 2nd parameter
listener.handleLiteralMapEntry(colon, token.next);
} else {
token = info.parse(token, this);
}
info = info.computeNext(token);
}
return token;
}
Token parseSendOrFunctionLiteral(Token token, IdentifierContext context) {
if (!mayParseFunctionExpressions) {
return parseSend(token, context);
}
TypeInfo typeInfo = computeType(token, false);
Token beforeName = typeInfo.skipType(token);
Token name = beforeName.next;
if (name.isIdentifier) {
TypeParamOrArgInfo typeParam = computeTypeParamOrArg(name);
Token next = typeParam.skip(name).next;
if (optional('(', next)) {
if (looksLikeFunctionBody(next.endGroup.next)) {
return parseFunctionLiteral(
token, beforeName, name, typeInfo, typeParam, context);
}
}
}
return parseSend(token, context);
}
Token ensureArguments(Token token) {
Token next = token.next;
if (!optional('(', next)) {
reportRecoverableError(
token, fasta.templateExpectedAfterButGot.withArguments('('));
next = rewriter.insertParens(token, false);
}
return parseArgumentsRest(next);
}
Token parseConstructorInvocationArguments(Token token) {
Token next = token.next;
if (!optional('(', next)) {
// Recovery: Check for invalid type parameters
TypeParamOrArgInfo typeArg = computeTypeParamOrArg(token);
if (typeArg == noTypeParamOrArg) {
reportRecoverableError(
token, fasta.templateExpectedAfterButGot.withArguments('('));
} else {
reportRecoverableError(
token, fasta.messageConstructorWithTypeArguments);
token = typeArg.parseArguments(token, this);
listener.handleInvalidTypeArguments(token);
next = token.next;
}
if (!optional('(', next)) {
next = rewriter.insertParens(token, false);
}
}
return parseArgumentsRest(next);
}
/// ```
/// newExpression:
/// 'new' type ('.' identifier)? arguments
/// ;
/// ```
Token parseNewExpression(Token token) {
Token newKeyword = token.next;
assert(optional('new', newKeyword));
listener.beginNewExpression(newKeyword);
token = parseConstructorReference(newKeyword);
token = parseConstructorInvocationArguments(token);
listener.endNewExpression(newKeyword);
return token;
}
Token parseImplicitCreationExpression(
Token token, TypeParamOrArgInfo typeArg) {
Token begin = token;
listener.beginImplicitCreationExpression(token);
token = parseConstructorReference(token, typeArg);
token = parseConstructorInvocationArguments(token);
listener.endImplicitCreationExpression(begin);
return token;
}
/// This method parses a list or map literal that is known to start with the
/// keyword 'const'.
///
/// ```
/// listLiteral:
/// 'const'? typeArguments? '[' (expressionList ','?)? ']'
/// ;
///
/// mapLiteral:
/// 'const'? typeArguments?
/// '{' (mapLiteralEntry (',' mapLiteralEntry)* ','?)? '}'
/// ;
///
/// mapLiteralEntry:
/// expression ':' expression
/// ;
/// ```
Token parseConstExpression(Token token) {
Token constKeyword = token = token.next;
assert(optional('const', constKeyword));
Token next = token.next;
final String value = next.stringValue;
if ((identical(value, '[')) || (identical(value, '[]'))) {
listener.beginConstLiteral(next);
listener.handleNoTypeArguments(next);
token = parseLiteralListSuffix(token, constKeyword);
listener.endConstLiteral(token.next);
return token;
}
if (identical(value, '{')) {
listener.beginConstLiteral(next);
listener.handleNoTypeArguments(next);
token = parseLiteralSetOrMapSuffix(token, constKeyword);
listener.endConstLiteral(token.next);
return token;
}
if (identical(value, '<')) {
listener.beginConstLiteral(next);
token = parseLiteralListSetMapOrFunction(token, constKeyword);
listener.endConstLiteral(token.next);
return token;
}
listener.beginConstExpression(constKeyword);
token = parseConstructorReference(token);
token = parseConstructorInvocationArguments(token);
listener.endConstExpression(constKeyword);
return token;
}
/// ```
/// intLiteral:
/// integer
/// ;
/// ```
Token parseLiteralInt(Token token) {
token = token.next;
assert(identical(token.kind, INT_TOKEN) ||
identical(token.kind, HEXADECIMAL_TOKEN));
listener.handleLiteralInt(token);
return token;
}
/// ```
/// doubleLiteral:
/// double
/// ;
/// ```
Token parseLiteralDouble(Token token) {
token = token.next;
assert(identical(token.kind, DOUBLE_TOKEN));
listener.handleLiteralDouble(token);
return token;
}
/// ```
/// stringLiteral:
/// (multilineString | singleLineString)+
/// ;
/// ```
Token parseLiteralString(Token token) {
assert(identical(token.next.kind, STRING_TOKEN));
bool old = mayParseFunctionExpressions;
mayParseFunctionExpressions = true;
token = parseSingleLiteralString(token);
int count = 1;
while (identical(token.next.kind, STRING_TOKEN)) {
token = parseSingleLiteralString(token);
count++;
}
if (count > 1) {
listener.handleStringJuxtaposition(count);
}
mayParseFunctionExpressions = old;
return token;
}
/// ```
/// symbolLiteral:
/// '#' (operator | (identifier ('.' identifier)*))
/// ;
/// ```
Token parseLiteralSymbol(Token token) {
Token hashToken = token = token.next;
assert(optional('#', hashToken));
listener.beginLiteralSymbol(hashToken);
Token next = token.next;
if (next.isUserDefinableOperator) {
listener.handleOperator(next);
listener.endLiteralSymbol(hashToken, 1);
return next;
} else if (optional('void', next)) {
listener.handleSymbolVoid(next);
listener.endLiteralSymbol(hashToken, 1);
return next;
} else {
int count = 1;
token = ensureIdentifier(token, IdentifierContext.literalSymbol);
while (optional('.', token.next)) {
count++;
token = ensureIdentifier(
token.next, IdentifierContext.literalSymbolContinuation);
}
listener.endLiteralSymbol(hashToken, count);
return token;
}
}
Token parseSingleLiteralString(Token token) {
token = token.next;
assert(identical(token.kind, STRING_TOKEN));
listener.beginLiteralString(token);
// Parsing the prefix, for instance 'x of 'x${id}y${id}z'
int interpolationCount = 0;
Token next = token.next;
int kind = next.kind;
while (kind != EOF_TOKEN) {
if (identical(kind, STRING_INTERPOLATION_TOKEN)) {
// Parsing ${expression}.
token = parseExpression(next).next;
if (!optional('}', token)) {
reportRecoverableError(
token, fasta.templateExpectedButGot.withArguments('}'));
token = next.endGroup;
}
listener.handleInterpolationExpression(next, token);
} else if (identical(kind, STRING_INTERPOLATION_IDENTIFIER_TOKEN)) {
// Parsing $identifier.
token = parseIdentifierExpression(next);
listener.handleInterpolationExpression(next, null);
} else {
break;
}
++interpolationCount;
// Parsing the infix/suffix, for instance y and z' of 'x${id}y${id}z'
token = parseStringPart(token);
next = token.next;
kind = next.kind;
}
listener.endLiteralString(interpolationCount, next);
return token;
}
Token parseIdentifierExpression(Token token) {
Token next = token.next;
if (next.kind == KEYWORD_TOKEN && identical(next.stringValue, "this")) {
listener.handleThisExpression(next, IdentifierContext.expression);
return next;
} else {
return parseSend(token, IdentifierContext.expression);
}
}
/// ```
/// booleanLiteral:
/// 'true' |
/// 'false'
/// ;
/// ```
Token parseLiteralBool(Token token) {
token = token.next;
assert(optional('false', token) || optional('true', token));
listener.handleLiteralBool(token);
return token;
}
/// ```
/// nullLiteral:
/// 'null'
/// ;
/// ```
Token parseLiteralNull(Token token) {
token = token.next;
assert(optional('null', token));
listener.handleLiteralNull(token);
return token;
}
Token parseSend(Token token, IdentifierContext context) {
Token beginToken = token = ensureIdentifier(token, context);
TypeParamOrArgInfo typeArg = computeMethodTypeArguments(token);
if (typeArg != noTypeParamOrArg) {
token = typeArg.parseArguments(token, this);
} else {
listener.handleNoTypeArguments(token.next);
}
token = parseArgumentsOpt(token);
listener.handleSend(beginToken, token.next);
return token;
}
Token skipArgumentsOpt(Token token) {
Token next = token.next;
listener.handleNoArguments(next);
if (optional('(', next)) {
return next.endGroup;
} else {
return token;
}
}
Token parseArgumentsOpt(Token token) {
Token next = token.next;
if (!optional('(', next)) {
listener.handleNoArguments(next);
return token;
} else {
return parseArguments(token);
}
}
/// ```
/// arguments:
/// '(' (argumentList ','?)? ')'
/// ;
///
/// argumentList:
/// namedArgument (',' namedArgument)* |
/// expressionList (',' namedArgument)*
/// ;
///
/// namedArgument:
/// label expression
/// ;
/// ```
Token parseArguments(Token token) {
return parseArgumentsRest(token.next);
}
Token parseArgumentsRest(Token token) {
Token begin = token;
assert(optional('(', begin));
listener.beginArguments(begin);
int argumentCount = 0;
bool hasSeenNamedArgument = false;
bool old = mayParseFunctionExpressions;
mayParseFunctionExpressions = true;
while (true) {
Token next = token.next;
if (optional(')', next)) {
token = next;
break;
}
Token colon = null;
if (optional(':', next.next)) {
token =
ensureIdentifier(token, IdentifierContext.namedArgumentReference)
.next;
colon = token;
hasSeenNamedArgument = true;
} else if (hasSeenNamedArgument) {
// Positional argument after named argument.
reportRecoverableError(next, fasta.messagePositionalAfterNamedArgument);
}
token = parseExpression(token);
next = token.next;
if (colon != null) listener.handleNamedArgument(colon);
++argumentCount;
if (!optional(',', next)) {
if (optional(')', next)) {
token = next;
break;
}
// Recovery
if (looksLikeExpressionStart(next)) {
// If this looks like the start of an expression,
// then report an error, insert the comma, and continue parsing.
next = rewriteAndRecover(
token,
fasta.templateExpectedButGot.withArguments(','),
new SyntheticToken(TokenType.COMMA, next.offset));
} else {
token = ensureCloseParen(token, begin);
break;
}
}
token = next;
}
assert(optional(')', token));
mayParseFunctionExpressions = old;
listener.endArguments(argumentCount, begin, token);
return token;
}
/// ```
/// typeTest::
/// 'is' '!'? type
/// ;
/// ```
Token parseIsOperatorRest(Token token) {
Token operator = token = token.next;
assert(optional('is', operator));
Token not = null;
if (optional('!', token.next)) {
not = token = token.next;
}
TypeInfo typeInfo = computeTypeAfterIsOrAs(token);
token = typeInfo.ensureTypeNotVoid(token, this);
listener.handleIsOperator(operator, not);
return skipChainedAsIsOperators(token);
}
TypeInfo computeTypeAfterIsOrAs(Token token) {
TypeInfo typeInfo = computeType(token, true);
if (typeInfo.isNullable) {
Token next = typeInfo.skipType(token).next;
if (!isOneOfOrEof(next, const [')', '?', ';', 'is', 'as'])) {
// TODO(danrubel): investigate other situations
// where `?` should be considered part of the type info
// rather than the start of a conditional expression.
typeInfo = typeInfo.asNonNullable;
}
}
return typeInfo;
}
/// ```
/// typeCast:
/// 'as' type
/// ;
/// ```
Token parseAsOperatorRest(Token token) {
Token operator = token = token.next;
assert(optional('as', operator));
TypeInfo typeInfo = computeTypeAfterIsOrAs(token);
token = typeInfo.ensureTypeNotVoid(token, this);
listener.handleAsOperator(operator);
return skipChainedAsIsOperators(token);
}
Token skipChainedAsIsOperators(Token token) {
while (true) {
Token next = token.next;
String value = next.stringValue;
if (!identical(value, 'is') && !identical(value, 'as')) {
return token;
}
// The is- and as-operators cannot be chained.
// TODO(danrubel): Consider a better error message.
reportRecoverableErrorWithToken(next, fasta.templateUnexpectedToken);
if (optional('!', next.next)) {
next = next.next;
}
TypeInfo typeInfo = computeTypeAfterIsOrAs(next);
token = typeInfo.skipType(next);
next = token.next;
value = next.stringValue;
}
}
/// Returns true if [token] could be the start of a function declaration
/// without a return type.
bool looksLikeLocalFunction(Token token) {
if (token.isIdentifier) {
if (optional('<', token.next)) {
TypeParamOrArgInfo typeParam = computeTypeParamOrArg(token);
if (typeParam == noTypeParamOrArg) {
return false;
}
token = typeParam.skip(token);
}
token = token.next;
if (optional('(', token)) {
token = token.endGroup.next;
return optional('{', token) ||
optional('=>', token) ||
optional('async', token) ||
optional('sync', token);
} else if (optional('=>', token)) {
// Recovery: Looks like a local function that is missing parenthesis.
return true;
}
}
return false;
}
/// Returns true if [token] could be the start of a function body.
bool looksLikeFunctionBody(Token token) {
return optional('{', token) ||
optional('=>', token) ||
optional('async', token) ||
optional('sync', token);
}
Token parseExpressionStatementOrConstDeclaration(final Token start) {
Token constToken = start.next;
assert(optional('const', constToken));
if (!isModifier(constToken.next)) {
TypeInfo typeInfo = computeType(constToken, false);
if (typeInfo == noType) {
Token next = constToken.next;
if (!next.isIdentifier) {
return parseExpressionStatement(start);
}
next = next.next;
if (!(optional('=', next) ||
// Recovery
next.isKeywordOrIdentifier ||
optional(';', next) ||
optional(',', next) ||
optional('{', next))) {
return parseExpressionStatement(start);
}
}
return parseExpressionStatementOrDeclarationAfterModifiers(
constToken, start, null, constToken, typeInfo, false);
}
return parseExpressionStatementOrDeclaration(start);
}
/// This method has two modes based upon [onlyParseVariableDeclarationStart].
///
/// If [onlyParseVariableDeclarationStart] is `false` (the default) then this
/// method will parse a local variable declaration, a local function,
/// or an expression statement, and then return the last consumed token.
///
/// If [onlyParseVariableDeclarationStart] is `true` then this method
/// will only parse the metadata, modifiers, and type of a local variable
/// declaration if it exists. It is the responsibility of the caller to
/// call [parseVariablesDeclarationRest] to finish parsing the local variable
/// declaration. If a local variable declaration is not found then this
/// method will return [start].
Token parseExpressionStatementOrDeclaration(final Token start,
[bool onlyParseVariableDeclarationStart = false]) {
Token token = start;
Token next = token.next;
if (optional('@', next)) {
token = parseMetadataStar(token);
next = token.next;
}
Token lateToken;
Token varFinalOrConst;
if (isModifier(next)) {
if (optional('var', next) ||
optional('final', next) ||
optional('const', next)) {
varFinalOrConst = token = token.next;
next = token.next;
} else if (optional('late', next)) {
lateToken = token = next;
next = token.next;
if (isModifier(next) &&
(optional('var', next) || optional('final', next))) {
varFinalOrConst = token = next;
next = token.next;
}
}
if (isModifier(next)) {
// Recovery
ModifierRecoveryContext context = new ModifierRecoveryContext(this)
..lateToken = lateToken
..varFinalOrConst = varFinalOrConst;
token = context.parseVariableDeclarationModifiers(token);
next = token.next;
lateToken = context.lateToken;
varFinalOrConst = context.varFinalOrConst;
context = null;
}
}
return parseExpressionStatementOrDeclarationAfterModifiers(token, start,
lateToken, varFinalOrConst, null, onlyParseVariableDeclarationStart);
}
/// See [parseExpressionStatementOrDeclaration]
Token parseExpressionStatementOrDeclarationAfterModifiers(
final Token beforeType,
final Token start,
final Token lateToken,
Token varFinalOrConst,
TypeInfo typeInfo,
bool onlyParseVariableDeclarationStart) {
typeInfo ??= computeType(beforeType, false);
Token token = typeInfo.skipType(beforeType);
Token next = token.next;
if (onlyParseVariableDeclarationStart) {
if (lateToken != null) {
reportRecoverableErrorWithToken(
lateToken, fasta.templateExtraneousModifier);
}
} else {
if (looksLikeLocalFunction(next)) {
// Parse a local function declaration.
if (varFinalOrConst != null) {
reportRecoverableErrorWithToken(
varFinalOrConst, fasta.templateExtraneousModifier);
} else if (lateToken != null) {
reportRecoverableErrorWithToken(
lateToken, fasta.templateExtraneousModifier);
}
if (!optional('@', start.next)) {
listener.beginMetadataStar(start.next);
listener.endMetadataStar(0);
}
Token beforeFormals =
computeTypeParamOrArg(next).parseVariables(next, this);
listener.beginLocalFunctionDeclaration(start.next);
token = typeInfo.parseType(beforeType, this);
return parseNamedFunctionRest(token, start.next, beforeFormals, false);
}
}
if (beforeType == start &&
typeInfo.isNullable &&
typeInfo.couldBeExpression) {
assert(optional('?', token));
assert(next.isKeywordOrIdentifier);
if (!next.isIdentifier) {
reportRecoverableError(
next, fasta.templateExpectedIdentifier.withArguments(next));
next = rewriter.insertSyntheticIdentifier(next);
}
Token afterIdentifier = next.next;
//
// found <typeref> `?` <identifier>
// with no annotations or modifiers preceeding it
//
if (optional('=', afterIdentifier)) {
//
// look past the next expression
// to determine if this is part of a conditional expression
//
Listener originalListener = listener;
listener = new ForwardingListener();
// TODO(danrubel): consider using TokenStreamGhostWriter here
Token afterExpression =
parseExpressionWithoutCascade(afterIdentifier).next;
listener = originalListener;
if (optional(':', afterExpression)) {
// Looks like part of a conditional expression.
// Drop the type information and reset the last consumed token.
typeInfo = noType;
token = start;
next = token.next;
}
} else if (!afterIdentifier.isKeyword &&
!isOneOfOrEof(afterIdentifier, const [';', ',', ')'])) {
// Looks like part of a conditional expression.
// Drop the type information and reset the last consumed token.
typeInfo = noType;
token = start;
next = token.next;
}
}
if (token == start) {
// If no annotation, modifier, or type, and this is not a local function
// then this must be an expression statement.
if (onlyParseVariableDeclarationStart) {
return start;
} else {
return parseExpressionStatement(start);
}
}
if (next.type.isBuiltIn &&
beforeType == start &&
typeInfo.couldBeExpression) {
// Detect expressions such as identifier `as` identifier
// and treat those as expressions.
if (optional('as', next) || optional('is', next)) {
int kind = next.next.kind;
if (EQ_TOKEN != kind &&
SEMICOLON_TOKEN != kind &&
COMMA_TOKEN != kind) {
if (onlyParseVariableDeclarationStart) {
if (!optional('in', next.next)) {
return start;
}
} else {
return parseExpressionStatement(start);
}
}
}
}
if (next.isIdentifier) {
// Only report these errors if there is an identifier. If there is not an
// identifier, then allow ensureIdentifier to report an error
// and don't report errors here.
if (varFinalOrConst == null) {
if (typeInfo == noType && lateToken == null) {
reportRecoverableError(next, fasta.messageMissingConstFinalVarOrType);
}
} else if (optional('var', varFinalOrConst)) {
if (typeInfo != noType) {
reportRecoverableError(varFinalOrConst, fasta.messageTypeAfterVar);
}
}
}
if (!optional('@', start.next)) {
listener.beginMetadataStar(start.next);
listener.endMetadataStar(0);
}
token = typeInfo.parseType(beforeType, this);
next = token.next;
listener.beginVariablesDeclaration(next, lateToken, varFinalOrConst);
if (!onlyParseVariableDeclarationStart) {
token = parseVariablesDeclarationRest(token, true);
}
return token;
}
Token parseVariablesDeclarationRest(Token token, bool endWithSemicolon) {
int count = 1;
token = parseOptionallyInitializedIdentifier(token);
while (optional(',', token.next)) {
token = parseOptionallyInitializedIdentifier(token.next);
++count;
}
if (endWithSemicolon) {
Token semicolon = ensureSemicolon(token);
listener.endVariablesDeclaration(count, semicolon);
return semicolon;
} else {
listener.endVariablesDeclaration(count, null);
return token;
}
}
Token parseOptionallyInitializedIdentifier(Token token) {
Token nameToken =
ensureIdentifier(token, IdentifierContext.localVariableDeclaration);
listener.beginInitializedIdentifier(nameToken);
token = parseVariableInitializerOpt(nameToken);
listener.endInitializedIdentifier(nameToken);
return token;
}
/// ```
/// ifStatement:
/// 'if' '(' expression ')' statement ('else' statement)?
/// ;
/// ```
Token parseIfStatement(Token token) {
Token ifToken = token.next;
assert(optional('if', ifToken));
listener.beginIfStatement(ifToken);
token = ensureParenthesizedCondition(ifToken);
listener.beginThenStatement(token.next);
token = parseStatement(token);
listener.endThenStatement(token);
Token elseToken = null;
if (optional('else', token.next)) {
elseToken = token.next;
listener.beginElseStatement(elseToken);
token = parseStatement(elseToken);
listener.endElseStatement(elseToken);
}
listener.endIfStatement(ifToken, elseToken);
return token;
}
/// ```
/// forStatement:
/// 'await'? 'for' '(' forLoopParts ')' statement
/// ;
///
/// forLoopParts:
/// localVariableDeclaration ';' expression? ';' expressionList?
/// | expression? ';' expression? ';' expressionList?
/// | localVariableDeclaration 'in' expression
/// | identifier 'in' expression
/// ;
///
/// forInitializerStatement:
/// localVariableDeclaration |
/// expression? ';'
/// ;
/// ```
Token parseForStatement(Token token, Token awaitToken) {
Token forToken = token = token.next;
assert(awaitToken == null || optional('await', awaitToken));
assert(optional('for', token));
listener.beginForStatement(forToken);
token = parseForLoopPartsStart(awaitToken, forToken);
Token identifier = token.next;
token = parseForLoopPartsMid(token, awaitToken, forToken);
if (optional('in', token.next) || optional(':', token.next)) {
// Process `for ( ... in ... )`
return parseForInRest(token, awaitToken, forToken, identifier);
} else {
// Process `for ( ... ; ... ; ... )`
return parseForRest(awaitToken, token, forToken);
}
}
/// Parse the start of a for loop control structure
/// from the open parenthesis up to but not including the identifier.
Token parseForLoopPartsStart(Token awaitToken, Token forToken) {
Token leftParenthesis = forToken.next;
if (!optional('(', leftParenthesis)) {
// Recovery
reportRecoverableError(
leftParenthesis, fasta.templateExpectedButGot.withArguments('('));
BeginToken openParen = rewriter.insertToken(
forToken,
new SyntheticBeginToken(
TokenType.OPEN_PAREN, leftParenthesis.offset));
Token token;
if (awaitToken != null) {
token = rewriter.insertSyntheticIdentifier(openParen);
token = rewriter.insertSyntheticKeyword(token, Keyword.IN);
token = rewriter.insertSyntheticIdentifier(token);
} else {
token = rewriter.insertSyntheticToken(openParen, TokenType.SEMICOLON);
token = rewriter.insertSyntheticToken(token, TokenType.SEMICOLON);
}
openParen.endGroup = token = rewriter.insertToken(token,
new SyntheticToken(TokenType.CLOSE_PAREN, leftParenthesis.offset));
token = rewriter.insertSyntheticIdentifier(token);
rewriter.insertSyntheticToken(token, TokenType.SEMICOLON);
leftParenthesis = openParen;
}
// Pass `true` so that the [parseExpressionStatementOrDeclaration] only
// parses the metadata, modifiers, and type of a local variable
// declaration if it exists. This enables capturing [beforeIdentifier]
// for later error reporting.
return parseExpressionStatementOrDeclaration(leftParenthesis, true);
}
/// Parse the remainder of the local variable declaration
/// or an expression if no local variable declaration was found.
Token parseForLoopPartsMid(Token token, Token awaitToken, Token forToken) {
if (token != forToken.next) {
token = parseVariablesDeclarationRest(token, false);
listener.handleForInitializerLocalVariableDeclaration(
token, optional('in', token.next));
} else if (optional(';', token.next)) {
listener.handleForInitializerEmptyStatement(token.next);
} else {
token = parseExpression(token);
listener.handleForInitializerExpressionStatement(
token,
optional('in', token.next) ||
optional(':', token.next) ||
// If this is an empty `await for`, we rewrite it into an
// `await for (_ in _)`.
(awaitToken != null && optional(')', token.next)));
}
Token next = token.next;
if (optional(';', next)) {
if (awaitToken != null) {
reportRecoverableError(awaitToken, fasta.messageInvalidAwaitFor);
}
} else if (!optional('in', next)) {
// Recovery
if (optional(':', next)) {
reportRecoverableError(next, fasta.messageColonInPlaceOfIn);
} else if (awaitToken != null) {
reportRecoverableError(
next, fasta.templateExpectedButGot.withArguments('in'));
token.setNext(
new SyntheticKeywordToken(Keyword.IN, next.offset)..setNext(next));
}
}
return token;
}
/// This method parses the portion of the forLoopParts that starts with the
/// first semicolon (the one that terminates the forInitializerStatement).
///
/// ```
/// forLoopParts:
/// localVariableDeclaration ';' expression? ';' expressionList?
/// | expression? ';' expression? ';' expressionList?
/// | localVariableDeclaration 'in' expression
/// | identifier 'in' expression
/// ;
/// ```
Token parseForRest(Token awaitToken, Token token, Token forToken) {
token = parseForLoopPartsRest(token, forToken, awaitToken);
listener.beginForStatementBody(token.next);
LoopState savedLoopState = loopState;
loopState = LoopState.InsideLoop;
token = parseStatement(token);
loopState = savedLoopState;
listener.endForStatementBody(token.next);
listener.endForStatement(token.next);
return token;
}
Token parseForLoopPartsRest(Token token, Token forToken, Token awaitToken) {
Token leftParenthesis = forToken.next;
assert(optional('for', forToken));
assert(optional('(', leftParenthesis));
Token leftSeparator = ensureSemicolon(token);
if (optional(';', leftSeparator.next)) {
token = parseEmptyStatement(leftSeparator);
} else {
token = parseExpressionStatement(leftSeparator);
}
int expressionCount = 0;
while (true) {
Token next = token.next;
if (optional(')', next)) {
token = next;
break;
}
token = parseExpression(token).next;
++expressionCount;
if (!optional(',', token)) {
break;
}
}
if (token != leftParenthesis.endGroup) {
reportRecoverableErrorWithToken(token, fasta.templateUnexpectedToken);
token = leftParenthesis.endGroup;
}
listener.handleForLoopParts(
forToken, leftParenthesis, leftSeparator, expressionCount);
return token;
}
/// This method parses the portion of the forLoopParts that starts with the
/// keyword 'in'. For the sake of recovery, we accept a colon in place of the
/// keyword.
///
/// ```
/// forLoopParts:
/// localVariableDeclaration ';' expression? ';' expressionList?
/// | expression? ';' expression? ';' expressionList?
/// | localVariableDeclaration 'in' expression
/// | identifier 'in' expression
/// ;
/// ```
Token parseForInRest(
Token token, Token awaitToken, Token forToken, Token identifier) {
token = parseForInLoopPartsRest(token, awaitToken, forToken, identifier);
listener.beginForInBody(token.next);
LoopState savedLoopState = loopState;
loopState = LoopState.InsideLoop;
token = parseStatement(token);
loopState = savedLoopState;
listener.endForInBody(token.next);
listener.endForIn(token.next);
return token;
}
Token parseForInLoopPartsRest(
Token token, Token awaitToken, Token forToken, Token identifier) {
Token inKeyword = token.next;
assert(optional('for', forToken));
assert(optional('(', forToken.next));
assert(optional('in', inKeyword) || optional(':', inKeyword));
if (!identifier.isIdentifier) {
reportRecoverableErrorWithToken(
identifier, fasta.templateExpectedIdentifier);
} else if (identifier != token) {
if (optional('=', identifier.next)) {
reportRecoverableError(
identifier.next, fasta.messageInitializedVariableInForEach);
} else {
reportRecoverableErrorWithToken(
identifier.next, fasta.templateUnexpectedToken);
}
} else if (awaitToken != null && !inAsync) {
// TODO(danrubel): consider reporting the error on awaitToken
reportRecoverableError(inKeyword, fasta.messageAwaitForNotAsync);
}
listener.beginForInExpression(inKeyword.next);
token = parseExpression(inKeyword);
token = ensureCloseParen(token, forToken.next);
listener.endForInExpression(token);
listener.handleForInLoopParts(
awaitToken, forToken, forToken.next, inKeyword);
return token;
}
/// ```
/// whileStatement:
/// 'while' '(' expression ')' statement
/// ;
/// ```
Token parseWhileStatement(Token token) {
Token whileToken = token.next;
assert(optional('while', whileToken));
listener.beginWhileStatement(whileToken);
token = ensureParenthesizedCondition(whileToken);
listener.beginWhileStatementBody(token.next);
LoopState savedLoopState = loopState;
loopState = LoopState.InsideLoop;
token = parseStatement(token);
loopState = savedLoopState;
listener.endWhileStatementBody(token.next);
listener.endWhileStatement(whileToken, token.next);
return token;
}
/// ```
/// doStatement:
/// 'do' statement 'while' '(' expression ')' ';'
/// ;
/// ```
Token parseDoWhileStatement(Token token) {
Token doToken = token.next;
assert(optional('do', doToken));
listener.beginDoWhileStatement(doToken);
listener.beginDoWhileStatementBody(doToken.next);
LoopState savedLoopState = loopState;
loopState = LoopState.InsideLoop;
token = parseStatement(doToken);
loopState = savedLoopState;
listener.endDoWhileStatementBody(token);
Token whileToken = token.next;
if (!optional('while', whileToken)) {
reportRecoverableError(
whileToken, fasta.templateExpectedButGot.withArguments('while'));
whileToken = rewriter.insertSyntheticKeyword(token, Keyword.WHILE);
}
token = ensureParenthesizedCondition(whileToken);
token = ensureSemicolon(token);
listener.endDoWhileStatement(doToken, whileToken, token);
return token;
}
/// ```
/// block:
/// '{' statement* '}'
/// ;
/// ```
Token parseBlock(Token token, BlockKind blockKind) {
Token begin = token = ensureBlock(token, null, blockKind.missingBlockName);
listener.beginBlock(begin, blockKind);
int statementCount = 0;
Token startToken = token.next;
while (notEofOrValue('}', startToken)) {
token = parseStatement(token);
if (identical(token.next, startToken)) {
// No progress was made, so we report the current token as being invalid
// and move forward.
token = token.next;
reportRecoverableError(
token, fasta.templateUnexpectedToken.withArguments(token));
}
++statementCount;
startToken = token.next;
}
token = token.next;
assert(token.isEof || optional('}', token));
listener.endBlock(statementCount, begin, token, blockKind);
return token;
}
Token parseInvalidBlock(Token token) {
Token begin = token.next;
assert(optional('{', begin));
// Parse and report the invalid block, but suppress errors
// because an error has already been reported by the caller.
Listener originalListener = listener;
listener = new ForwardingListener(listener)..forwardErrors = false;
// The scanner ensures that `{` always has a closing `}`.
token = parseBlock(token, BlockKind.invalid);
listener = originalListener;
listener.handleInvalidTopLevelBlock(begin);
return token;
}
/// Determine if the following tokens look like an 'await' expression
/// and not a local variable or local function declaration.
bool looksLikeAwaitExpression(Token token) {
token = token.next;
assert(optional('await', token));
token = token.next;
// TODO(danrubel): Consider parsing the potential expression following
// the `await` token once doing so does not modify the token stream.
// For now, use simple look ahead and ensure no false positives.
if (token.isIdentifier) {
token = token.next;
if (optional('(', token)) {
token = token.endGroup.next;
if (isOneOf(token, [';', '.', '..', '?', '?.'])) {
return true;
}
} else if (isOneOf(token, ['.', ')', ']'])) {
return true;
}
}
return false;
}
/// ```
/// awaitExpression:
/// 'await' unaryExpression
/// ;
/// ```
Token parseAwaitExpression(Token token, bool allowCascades) {
Token awaitToken = token.next;
assert(optional('await', awaitToken));
listener.beginAwaitExpression(awaitToken);
token = parsePrecedenceExpression(
awaitToken, POSTFIX_PRECEDENCE, allowCascades);
if (inAsync) {
listener.endAwaitExpression(awaitToken, token.next);
} else {
fasta.MessageCode errorCode = fasta.messageAwaitNotAsync;
reportRecoverableError(awaitToken, errorCode);
listener.endInvalidAwaitExpression(awaitToken, token.next, errorCode);
}
return token;
}
/// ```
/// throwExpression:
/// 'throw' expression
/// ;
///
/// throwExpressionWithoutCascade:
/// 'throw' expressionWithoutCascade
/// ;
/// ```
Token parseThrowExpression(Token token, bool allowCascades) {
Token throwToken = token.next;
assert(optional('throw', throwToken));
if (optional(';', throwToken.next)) {
// TODO(danrubel): Find a better way to intercept the parseExpression
// recovery to generate this error message rather than explicitly
// checking the next token as we are doing here.
reportRecoverableError(
throwToken.next, fasta.messageMissingExpressionInThrow);
rewriter.insertToken(
throwToken,
new SyntheticStringToken(
TokenType.STRING, '""', throwToken.next.charOffset, 0));
}
token = allowCascades
? parseExpression(throwToken)
: parseExpressionWithoutCascade(throwToken);
listener.handleThrowExpression(throwToken, token.next);
return token;
}
/// ```
/// rethrowStatement:
/// 'rethrow' ';'
/// ;
/// ```
Token parseRethrowStatement(Token token) {
Token throwToken = token.next;
assert(optional('rethrow', throwToken));
listener.beginRethrowStatement(throwToken);
token = ensureSemicolon(throwToken);
listener.endRethrowStatement(throwToken, token);
return token;
}
/// ```
/// tryStatement:
/// 'try' block (onPart+ finallyPart? | finallyPart)
/// ;
///
/// onPart:
/// catchPart block |
/// 'on' type catchPart? block
/// ;
///
/// catchPart:
/// 'catch' '(' identifier (',' identifier)? ')'
/// ;
///
/// finallyPart:
/// 'finally' block
/// ;
/// ```
Token parseTryStatement(Token token) {
Token tryKeyword = token.next;
assert(optional('try', tryKeyword));
listener.beginTryStatement(tryKeyword);
Token lastConsumed = parseBlock(tryKeyword, BlockKind.tryStatement);
token = lastConsumed.next;
int catchCount = 0;
String value = token.stringValue;
while (identical(value, 'catch') || identical(value, 'on')) {
listener.beginCatchClause(token);
Token onKeyword = null;
if (identical(value, 'on')) {
// 'on' type catchPart?
onKeyword = token;
lastConsumed = computeType(token, true).ensureTypeNotVoid(token, this);
token = lastConsumed.next;
value = token.stringValue;
}
Token catchKeyword = null;
Token comma = null;
if (identical(value, 'catch')) {
catchKeyword = token;
Token openParens = catchKeyword.next;
if (!optional("(", openParens)) {
reportRecoverableError(openParens, fasta.messageCatchSyntax);
openParens = rewriter.insertParens(catchKeyword, true);
}
Token exceptionName = openParens.next;
if (exceptionName.kind != IDENTIFIER_TOKEN) {
exceptionName = IdentifierContext.catchParameter
.ensureIdentifier(openParens, this);
}
if (optional(")", exceptionName.next)) {
// OK: `catch (identifier)`.
} else {
comma = exceptionName.next;
if (!optional(",", comma)) {
// Recovery
if (!exceptionName.isSynthetic) {
reportRecoverableError(comma, fasta.messageCatchSyntax);
}
// TODO(danrubel): Consider inserting `on` clause if
// exceptionName is preceded by type and followed by a comma.
// Then this
// } catch (E e, t) {
// will recover to
// } on E catch (e, t) {
// with a detailed explanation for the user in the error
// indicating what they should do to fix the code.
// TODO(danrubel): Consider inserting synthetic identifier if
// exceptionName is a non-synthetic identifier followed by `.`.
// Then this
// } catch (
// e.f();
// will recover to
// } catch (_s_) {}
// e.f();
// rather than
// } catch (e) {}
// _s_.f();
if (openParens.endGroup.isSynthetic) {
// The scanner did not place the synthetic ')' correctly.
rewriter.moveSynthetic(exceptionName, openParens.endGroup);
comma = null;
} else {
comma =
rewriter.insertSyntheticToken(exceptionName, TokenType.COMMA);
}
}
if (comma != null) {
Token traceName = comma.next;
if (traceName.kind != IDENTIFIER_TOKEN) {
traceName = IdentifierContext.catchParameter
.ensureIdentifier(comma, this);
}
if (!optional(")", traceName.next)) {
// Recovery
if (!traceName.isSynthetic) {
reportRecoverableError(
traceName.next, fasta.messageCatchSyntaxExtraParameters);
}
if (openParens.endGroup.isSynthetic) {
// The scanner did not place the synthetic ')' correctly.
rewriter.moveSynthetic(traceName, openParens.endGroup);
}
}
}
}
lastConsumed = parseFormalParameters(catchKeyword, MemberKind.Catch);
token = lastConsumed.next;
}
listener.endCatchClause(token);
lastConsumed = parseBlock(lastConsumed, BlockKind.catchClause);
token = lastConsumed.next;
++catchCount;
listener.handleCatchBlock(onKeyword, catchKeyword, comma);
value = token.stringValue; // while condition
}
Token finallyKeyword = null;
if (optional('finally', token)) {
finallyKeyword = token;
lastConsumed = parseBlock(token, BlockKind.finallyClause);
token = lastConsumed.next;
listener.handleFinallyBlock(finallyKeyword);
} else {
if (catchCount == 0) {
reportRecoverableError(tryKeyword, fasta.messageOnlyTry);
}
}
listener.endTryStatement(catchCount, tryKeyword, finallyKeyword);
return lastConsumed;
}
/// ```
/// switchStatement:
/// 'switch' parenthesizedExpression switchBlock
/// ;
/// ```
Token parseSwitchStatement(Token token) {
Token switchKeyword = token.next;
assert(optional('switch', switchKeyword));
listener.beginSwitchStatement(switchKeyword);
token = ensureParenthesizedCondition(switchKeyword);
LoopState savedLoopState = loopState;
if (loopState == LoopState.OutsideLoop) {
loopState = LoopState.InsideSwitch;
}
token = parseSwitchBlock(token);
loopState = savedLoopState;
listener.endSwitchStatement(switchKeyword, token);
return token;
}
/// ```
/// switchBlock:
/// '{' switchCase* defaultCase? '}'
/// ;
/// ```
Token parseSwitchBlock(Token token) {
Token beginSwitch = token = ensureBlock(token, null, 'switch statement');
listener.beginSwitchBlock(beginSwitch);
int caseCount = 0;
Token defaultKeyword = null;
Token colonAfterDefault = null;
while (notEofOrValue('}', token.next)) {
Token beginCase = token.next;
int expressionCount = 0;
int labelCount = 0;
Token peek = peekPastLabels(beginCase);
while (true) {
// Loop until we find something that can't be part of a switch case.
String value = peek.stringValue;
if (identical(value, 'default')) {
while (!identical(token.next, peek)) {
token = parseLabel(token);
labelCount++;
}
if (defaultKeyword != null) {
reportRecoverableError(
token.next, fasta.messageSwitchHasMultipleDefaults);
}
defaultKeyword = token.next;
colonAfterDefault = token = ensureColon(defaultKeyword);
peek = token.next;
break;
} else if (identical(value, 'case')) {
while (!identical(token.next, peek)) {
token = parseLabel(token);
labelCount++;
}
Token caseKeyword = token.next;
if (defaultKeyword != null) {
reportRecoverableError(
caseKeyword, fasta.messageSwitchHasCaseAfterDefault);
}
listener.beginCaseExpression(caseKeyword);
token = parseExpression(caseKeyword);
token = ensureColon(token);
listener.endCaseExpression(token);
listener.handleCaseMatch(caseKeyword, token);
expressionCount++;
peek = peekPastLabels(token.next);
} else if (expressionCount > 0) {
break;
} else {
// Recovery
reportRecoverableError(
peek, fasta.templateExpectedToken.withArguments("case"));
Token endGroup = beginSwitch.endGroup;
while (token.next != endGroup) {
token = token.next;
}
peek = peekPastLabels(token.next);
break;
}
}
token = parseStatementsInSwitchCase(token, peek, beginCase, labelCount,
expressionCount, defaultKeyword, colonAfterDefault);
++caseCount;
}
token = token.next;
listener.endSwitchBlock(caseCount, beginSwitch, token);
assert(token.isEof || optional('}', token));
return token;
}
/// Peek after the following labels (if any). The following token
/// is used to determine if the labels belong to a statement or a
/// switch case.
Token peekPastLabels(Token token) {
while (token.isIdentifier && optional(':', token.next)) {
token = token.next.next;
}
return token;
}
/// Parse statements after a switch `case:` or `default:`.
Token parseStatementsInSwitchCase(
Token token,
Token peek,
Token begin,
int labelCount,
int expressionCount,
Token defaultKeyword,
Token colonAfterDefault) {
listener.beginSwitchCase(labelCount, expressionCount, begin);
// Finally zero or more statements.
int statementCount = 0;
while (!identical(token.next.kind, EOF_TOKEN)) {
String value = peek.stringValue;
if ((identical(value, 'case')) ||
(identical(value, 'default')) ||
((identical(value, '}')) && (identical(token.next, peek)))) {
// A label just before "}" will be handled as a statement error.
break;
} else {
Token startToken = token.next;
token = parseStatement(token);
Token next = token.next;
if (identical(next, startToken)) {
// No progress was made, so we report the current token as being
// invalid and move forward.
reportRecoverableError(
next, fasta.templateUnexpectedToken.withArguments(next));
token = next;
}
++statementCount;
}
peek = peekPastLabels(token.next);
}
listener.endSwitchCase(labelCount, expressionCount, defaultKeyword,
colonAfterDefault, statementCount, begin, token.next);
return token;
}
/// ```
/// breakStatement:
/// 'break' identifier? ';'
/// ;
/// ```
Token parseBreakStatement(Token token) {
Token breakKeyword = token = token.next;
assert(optional('break', breakKeyword));
bool hasTarget = false;
if (token.next.isIdentifier) {
token = ensureIdentifier(token, IdentifierContext.labelReference);
hasTarget = true;
} else if (!isBreakAllowed) {
reportRecoverableError(breakKeyword, fasta.messageBreakOutsideOfLoop);
}
token = ensureSemicolon(token);
listener.handleBreakStatement(hasTarget, breakKeyword, token);
return token;
}
/// ```
/// assertion:
/// 'assert' '(' expression (',' expression)? ','? ')'
/// ;
/// ```
Token parseAssert(Token token, Assert kind) {
token = token.next;
assert(optional('assert', token));
listener.beginAssert(token, kind);
Token assertKeyword = token;
Token leftParenthesis = token.next;
if (!optional('(', leftParenthesis)) {
// Recovery
reportRecoverableError(
leftParenthesis, fasta.templateExpectedButGot.withArguments('('));
leftParenthesis = rewriter.insertParens(token, true);
}
token = leftParenthesis;
Token commaToken = null;
bool old = mayParseFunctionExpressions;
mayParseFunctionExpressions = true;
token = parseExpression(token);
if (optional(',', token.next)) {
token = token.next;
if (!optional(')', token.next)) {
commaToken = token;
token = parseExpression(token);
if (optional(',', token.next)) {
// Trailing comma is ignored.
token = token.next;
}
}
}
Token endGroup = leftParenthesis.endGroup;
if (token.next == endGroup) {
token = endGroup;
} else {
// Recovery
if (endGroup.isSynthetic) {
// The scanner did not place the synthetic ')' correctly, so move it.
token = rewriter.moveSynthetic(token, endGroup);
} else {
reportRecoverableErrorWithToken(
token.next, fasta.templateUnexpectedToken);
token = endGroup;
}
}
assert(optional(')', token));
mayParseFunctionExpressions = old;
if (kind == Assert.Expression) {
reportRecoverableError(assertKeyword, fasta.messageAssertAsExpression);
} else if (kind == Assert.Statement) {
ensureSemicolon(token);
}
listener.endAssert(
assertKeyword, kind, leftParenthesis, commaToken, token.next);
return token;
}
/// ```
/// assertStatement:
/// assertion ';'
/// ;
/// ```
Token parseAssertStatement(Token token) {
assert(optional('assert', token.next));
// parseAssert ensures that there is a trailing semicolon.
return parseAssert(token, Assert.Statement).next;
}
/// ```
/// continueStatement:
/// 'continue' identifier? ';'
/// ;
/// ```
Token parseContinueStatement(Token token) {
Token continueKeyword = token = token.next;
assert(optional('continue', continueKeyword));
bool hasTarget = false;
if (token.next.isIdentifier) {
token = ensureIdentifier(token, IdentifierContext.labelReference);
hasTarget = true;
if (!isContinueWithLabelAllowed) {
reportRecoverableError(
continueKeyword, fasta.messageContinueOutsideOfLoop);
}
} else if (!isContinueAllowed) {
reportRecoverableError(
continueKeyword,
loopState == LoopState.InsideSwitch
? fasta.messageContinueWithoutLabelInCase
: fasta.messageContinueOutsideOfLoop);
}
token = ensureSemicolon(token);
listener.handleContinueStatement(hasTarget, continueKeyword, token);
return token;
}
/// ```
/// emptyStatement:
/// ';'
/// ;
/// ```
Token parseEmptyStatement(Token token) {
token = token.next;
assert(optional(';', token));
listener.handleEmptyStatement(token);
return token;
}
/// Given a token ([beforeToken]) that is known to be before another [token],
/// return the token that is immediately before the [token].
Token previousToken(Token beforeToken, Token token) {
Token next = beforeToken.next;
while (next != token && next != beforeToken) {
beforeToken = next;
next = beforeToken.next;
}
return beforeToken;
}
/// Recover from finding an operator declaration missing the `operator`
/// keyword. The metadata for the member, if any, has already been parsed
/// (and events have already been generated).
Token parseInvalidOperatorDeclaration(
Token beforeStart,
Token externalToken,
Token staticToken,
Token covariantToken,
Token lateToken,
Token varFinalOrConst,
Token beforeType,
DeclarationKind kind,
String enclosingDeclarationName) {
TypeInfo typeInfo = computeType(beforeStart, false, true);
Token beforeName = typeInfo.skipType(beforeType);
Token next = beforeName.next;
if (optional('operator', next)) {
next = next.next;
} else {
// The 'operator' keyword is missing, but we may or may not have a type
// before the token that is the actual operator.
Token operator = next;
if (!next.isOperator && next.next.isOperator) {
beforeName = next;
operator = next.next;
}
reportRecoverableError(operator, fasta.messageMissingOperatorKeyword);
rewriter.insertSyntheticKeyword(beforeName, Keyword.OPERATOR);
// Having inserted the keyword the type now possibly compute differently.
typeInfo = computeType(beforeStart, true, true);
beforeName = typeInfo.skipType(beforeType);
next = beforeName.next;
// The 'next' token can be the just-inserted 'operator' keyword.
// If it is, change it so it points to the actual operator.
if (!next.isOperator &&
next.next.isOperator &&
identical(next.stringValue, 'operator')) {
next = next.next;
}
}
assert((next.isOperator && next.endGroup == null) ||
optional('===', next) ||
optional('!==', next));
Token token = parseMethod(
beforeStart,
externalToken,
staticToken,
covariantToken,
lateToken,
varFinalOrConst,
beforeType,
typeInfo,
null,
beforeName.next,
kind,
enclosingDeclarationName);
listener.endMember();
return token;
}
/// Recover from finding an invalid class member. The metadata for the member,
/// if any, has already been parsed (and events have already been generated).
/// The member was expected to start with the token after [token].
Token recoverFromInvalidMember(
Token token,
Token beforeStart,
Token externalToken,
Token staticToken,
Token covariantToken,
Token lateToken,
Token varFinalOrConst,
Token beforeType,
TypeInfo typeInfo,
Token getOrSet,
DeclarationKind kind,
String enclosingDeclarationName) {
Token next = token.next;
String value = next.stringValue;
if (identical(value, 'class')) {
return reportAndSkipClassInClass(next);
} else if (identical(value, 'enum')) {
return reportAndSkipEnumInClass(next);
} else if (identical(value, 'typedef')) {
return reportAndSkipTypedefInClass(next);
} else if (next.isOperator && next.endGroup == null) {
return parseInvalidOperatorDeclaration(
beforeStart,
externalToken,
staticToken,
covariantToken,
lateToken,
varFinalOrConst,
beforeType,
kind,
enclosingDeclarationName);
}
if (getOrSet != null ||
identical(value, '(') ||
identical(value, '=>') ||
identical(value, '{')) {
token = parseMethod(
beforeStart,
externalToken,
staticToken,
covariantToken,
lateToken,
varFinalOrConst,
beforeType,
typeInfo,
getOrSet,
token.next,
kind,
enclosingDeclarationName);
} else if (token == beforeStart) {
// TODO(danrubel): Provide a more specific error message for extra ';'.
reportRecoverableErrorWithToken(next, fasta.templateExpectedClassMember);
listener.handleInvalidMember(next);
if (!identical(value, '}')) {
// Ensure we make progress.
token = next;
}
} else {
token = parseFields(
beforeStart,
externalToken,
staticToken,
covariantToken,
lateToken,
varFinalOrConst,
beforeType,
typeInfo,
token.next,
kind);
}
listener.endMember();
return token;
}
/// Report that the nesting depth of the code being parsed is too large for
/// the parser to safely handle. Return the next `}` or EOF.
Token recoverFromStackOverflow(Token token) {
Token next = token.next;
reportRecoverableError(next, fasta.messageStackOverflow);
next = rewriter.insertSyntheticToken(token, TokenType.SEMICOLON);
listener.handleEmptyStatement(next);
while (notEofOrValue('}', next)) {
token = next;
next = token.next;
}
return token;
}
void reportRecoverableError(Token token, Message message) {
// Find a non-synthetic token on which to report the error.
token = findNonZeroLengthToken(token);
listener.handleRecoverableError(message, token, token);
}
void reportRecoverableErrorWithToken(
Token token, Template<_MessageWithArgument<Token>> template) {
// Find a non-synthetic token on which to report the error.
token = findNonZeroLengthToken(token);
listener.handleRecoverableError(
template.withArguments(token), token, token);
}
Token reportAllErrorTokens(Token token) {
while (token is ErrorToken) {
listener.handleErrorToken(token);
token = token.next;
}
return token;
}
Token skipErrorTokens(Token token) {
while (token is ErrorToken) {
token = token.next;
}
return token;
}
Token parseInvalidTopLevelDeclaration(Token token) {
Token next = token.next;
reportRecoverableErrorWithToken(
next,
optional(';', next)
? fasta.templateUnexpectedToken
: fasta.templateExpectedDeclaration);
if (optional('{', next)) {
next = parseInvalidBlock(token);
}
listener.handleInvalidTopLevelDeclaration(next);
return next;
}
Token reportAndSkipClassInClass(Token token) {
assert(optional('class', token));
reportRecoverableError(token, fasta.messageClassInClass);
listener.handleInvalidMember(token);
Token next = token.next;
// If the declaration appears to be a valid class declaration
// then skip the entire declaration so that we only generate the one
// error (above) rather than a plethora of unhelpful errors.
if (next.isIdentifier) {
// skip class name
token = next;
next = token.next;
// TODO(danrubel): consider parsing (skipping) the class header
// with a recovery listener so that no events are generated
if (optional('{', next) && next.endGroup != null) {
// skip class body
token = next.endGroup;
}
}
return token;
}
Token reportAndSkipEnumInClass(Token token) {
assert(optional('enum', token));
reportRecoverableError(token, fasta.messageEnumInClass);
listener.handleInvalidMember(token);
Token next = token.next;
// If the declaration appears to be a valid enum declaration
// then skip the entire declaration so that we only generate the one
// error (above) rather than a plethora of unhelpful errors.
if (next.isIdentifier) {
// skip enum name
token = next;
next = token.next;
if (optional('{', next) && next.endGroup != null) {
// TODO(danrubel): Consider replacing this `skip enum` functionality
// with something that can parse and resolve the declaration
// even though it is in a class context
token = next.endGroup;
}
}
return token;
}
Token reportAndSkipTypedefInClass(Token token) {
assert(optional('typedef', token));
reportRecoverableError(token, fasta.messageTypedefInClass);
listener.handleInvalidMember(token);
// TODO(brianwilkerson): If the declaration appears to be a valid typedef
// then skip the entire declaration so that we generate a single error
// (above) rather than many unhelpful errors.
return token;
}
/// Create a short token chain from the [beginToken] and [endToken] and return
/// the [beginToken].
Token link(BeginToken beginToken, Token endToken) {
beginToken.setNext(endToken);
beginToken.endGroup = endToken;
return beginToken;
}
/// Create and return a token whose next token is the given [token].
Token syntheticPreviousToken(Token token) {
// Return the previous token if there is one so that any token inserted
// before `token` will be properly inserted into the token stream.
// TODO(danrubel): remove this once all methods have been converted to
// use and return the last token consumed and the `previous` field
// has been removed.
if (token.previous != null) {
return token.previous;
}
Token before = new Token.eof(-1);
before.next = token;
return before;
}
/// Return the first dartdoc comment token preceding the given token
/// or `null` if no dartdoc token is found.
Token findDartDoc(Token token) {
Token comments = token.precedingComments;
Token dartdoc = null;
bool isMultiline = false;
while (comments != null) {
String lexeme = comments.lexeme;
if (lexeme.startsWith('///')) {
if (!isMultiline) {
dartdoc = comments;
isMultiline = true;
}
} else if (lexeme.startsWith('/**')) {
dartdoc = comments;
isMultiline = false;
}
comments = comments.next;
}
return dartdoc;
}
/// Parse the comment references in a sequence of comment tokens
/// where [dartdoc] (not null) is the first token in the sequence.
/// Return the number of comment references parsed.
int parseCommentReferences(Token dartdoc) {
return dartdoc.lexeme.startsWith('///')
? parseReferencesInSingleLineComments(dartdoc)
: parseReferencesInMultiLineComment(dartdoc);
}
/// Parse the comment references in a multi-line comment token.
/// Return the number of comment references parsed.
int parseReferencesInMultiLineComment(Token multiLineDoc) {
String comment = multiLineDoc.lexeme;
assert(comment.startsWith('/**'));
int count = 0;
int length = comment.length;
int start = 3;
bool inCodeBlock = false;
int codeBlock = comment.indexOf('```', 3);
if (codeBlock == -1) {
codeBlock = length;
}
while (start < length) {
if (isWhitespace(comment.codeUnitAt(start))) {
++start;
continue;
}
int end = comment.indexOf('\n', start);
if (end == -1) {
end = length;
}
if (codeBlock < end) {
inCodeBlock = !inCodeBlock;
codeBlock = comment.indexOf('```', end);
if (codeBlock == -1) {
codeBlock = length;
}
}
if (!inCodeBlock && !comment.startsWith('* ', start)) {
count += parseCommentReferencesInText(multiLineDoc, start, end);
}
start = end + 1;
}
return count;
}
/// Parse the comment references in a sequence of single line comment tokens
/// where [token] is the first comment token in the sequence.
/// Return the number of comment references parsed.
int parseReferencesInSingleLineComments(Token token) {
int count = 0;
bool inCodeBlock = false;
while (token != null && !token.isEof) {
String comment = token.lexeme;
if (comment.startsWith('///')) {
if (comment.indexOf('```', 3) != -1) {
inCodeBlock = !inCodeBlock;
}
if (!inCodeBlock && !comment.startsWith('/// ')) {
count += parseCommentReferencesInText(token, 3, comment.length);
}
}
token = token.next;
}
return count;
}
/// Parse the comment references in the text between [start] inclusive
/// and [end] exclusive. Return a count indicating how many were parsed.
int parseCommentReferencesInText(Token commentToken, int start, int end) {
String comment = commentToken.lexeme;
int count = 0;
int index = start;
while (index < end) {
int ch = comment.codeUnitAt(index);
if (ch == 0x5B /* `[` */) {
++index;
if (index < end && comment.codeUnitAt(index) == 0x3A /* `:` */) {
// Skip old-style code block.
index = comment.indexOf(':]', index + 1) + 1;
if (index == 0 || index > end) {
break;
}
} else {
int referenceStart = index;
index = comment.indexOf(']', index);
if (index == -1 || index >= end) {
// Recovery: terminating ']' is not typed yet.
index = findReferenceEnd(comment, referenceStart, end);
}
if (ch != 0x27 /* `'` */ && ch != 0x22 /* `"` */) {
if (isLinkText(comment, index)) {
// TODO(brianwilkerson) Handle the case where there's a library
// URI in the link text.
} else {
listener.handleCommentReferenceText(
comment.substring(referenceStart, index),
commentToken.charOffset + referenceStart);
++count;
}
}
}
} else if (ch == 0x60 /* '`' */) {
// Skip inline code block if there is both starting '`' and ending '`'
int endCodeBlock = comment.indexOf('`', index + 1);
if (endCodeBlock != -1 && endCodeBlock < end) {
index = endCodeBlock;
}
}
++index;
}
return count;
}
/// Given a comment reference without a closing `]`,
/// search for a possible place where `]` should be.
int findReferenceEnd(String comment, int index, int end) {
// Find the end of the identifier if there is one
if (index >= end || !isLetter(comment.codeUnitAt(index))) {
return index;
}
while (index < end && isLetterOrDigit(comment.codeUnitAt(index))) {
++index;
}
// Check for a trailing `.`
if (index >= end || comment.codeUnitAt(index) != 0x2E /* `.` */) {
return index;
}
++index;
// Find end of the identifier after the `.`
if (index >= end || !isLetter(comment.codeUnitAt(index))) {
return index;
}
++index;
while (index < end && isLetterOrDigit(comment.codeUnitAt(index))) {
++index;
}
return index;
}
/// Parse the tokens in a single comment reference and generate either a
/// `handleCommentReference` or `handleNoCommentReference` event.
/// Return `true` if a comment reference was successfully parsed.
bool parseOneCommentReference(Token token, int referenceOffset) {
Token begin = token;
Token newKeyword = null;
if (optional('new', token)) {
newKeyword = token;
token = token.next;
}
Token prefix, period;
if (token.isIdentifier && optional('.', token.next)) {
prefix = token;
period = token.next;
token = period.next;
}
if (token.isEof) {
// Recovery: Insert a synthetic identifier for code completion
token = rewriter.insertSyntheticIdentifier(
period ?? newKeyword ?? syntheticPreviousToken(token));
if (begin == token.next) {
begin = token;
}
}
Token operatorKeyword = null;
if (optional('operator', token)) {
operatorKeyword = token;
token = token.next;
}
if (token.isUserDefinableOperator) {
if (token.next.isEof) {
parseOneCommentReferenceRest(
begin, referenceOffset, newKeyword, prefix, period, token);
return true;
}
} else {
token = operatorKeyword ?? token;
if (token.next.isEof) {
if (token.isIdentifier) {
parseOneCommentReferenceRest(
begin, referenceOffset, newKeyword, prefix, period, token);
return true;
}
Keyword keyword = token.keyword;
if (newKeyword == null &&
prefix == null &&
(keyword == Keyword.THIS ||
keyword == Keyword.NULL ||
keyword == Keyword.TRUE ||
keyword == Keyword.FALSE)) {
// TODO(brianwilkerson) If we want to support this we will need to
// extend the definition of CommentReference to take an expression
// rather than an identifier. For now we just ignore it to reduce the
// number of errors produced, but that's probably not a valid long
// term approach.
}
}
}
listener.handleNoCommentReference();
return false;
}
void parseOneCommentReferenceRest(
Token begin,
int referenceOffset,
Token newKeyword,
Token prefix,
Token period,
Token identifierOrOperator) {
// Adjust the token offsets to match the enclosing comment token.
Token token = begin;
do {
token.offset += referenceOffset;
token = token.next;
} while (!token.isEof);
listener.handleCommentReference(
newKeyword, prefix, period, identifierOrOperator);
}
/// Given that we have just found bracketed text within the given [comment],
/// look to see whether that text is (a) followed by a parenthesized link
/// address, (b) followed by a colon, or (c) followed by optional whitespace
/// and another square bracket. The [rightIndex] is the index of the right
/// bracket. Return `true` if the bracketed text is followed by a link
/// address.
///
/// This method uses the syntax described by the
/// <a href="http://daringfireball.net/projects/markdown/syntax">markdown</a>
/// project.
bool isLinkText(String comment, int rightIndex) {
int length = comment.length;
int index = rightIndex + 1;
if (index >= length) {
return false;
}
int ch = comment.codeUnitAt(index);
if (ch == 0x28 || ch == 0x3A) {
return true;
}
while (isWhitespace(ch)) {
index = index + 1;
if (index >= length) {
return false;
}
ch = comment.codeUnitAt(index);
}
return ch == 0x5B;
}
}
// TODO(ahe): Remove when analyzer supports generalized function syntax.
typedef _MessageWithArgument<T> = Message Function(T);