blob: 5ecd8d468f6353b1a69a98008587d95356ce9043 [file] [log] [blame]
// Copyright (c) 2014, 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 analyzer.parser;
import 'dart:collection';
import "dart:math" as math;
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/standard_ast_factory.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/dart/error/syntactic_errors.dart';
import 'package:analyzer/src/dart/scanner/reader.dart';
import 'package:analyzer/src/dart/scanner/scanner.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/fasta/ast_builder.dart';
import 'package:analyzer/src/generated/engine.dart' show AnalysisEngine;
import 'package:analyzer/src/generated/java_core.dart';
import 'package:analyzer/src/generated/java_engine.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:front_end/src/fasta/parser/identifier_context.dart' as fasta;
import 'package:front_end/src/fasta/parser/member_kind.dart' as fasta;
import 'package:front_end/src/fasta/parser/parser.dart' as fasta;
import 'package:front_end/src/fasta/parser/type_info.dart' as fasta;
import 'package:front_end/src/fasta/scanner.dart' as fasta;
export 'package:analyzer/src/dart/ast/utilities.dart' show ResolutionCopier;
export 'package:analyzer/src/dart/error/syntactic_errors.dart';
part 'parser_fasta.dart';
/// A simple data-holder for a method that needs to return multiple values.
class CommentAndMetadata {
/// The documentation comment that was parsed, or `null` if none was given.
final Comment comment;
/// The metadata that was parsed, or `null` if none was given.
final List<Annotation> metadata;
/// Initialize a newly created holder with the given [comment] and [metadata].
CommentAndMetadata(this.comment, this.metadata);
/// Return `true` if some metadata was parsed.
bool get hasMetadata => metadata != null && metadata.isNotEmpty;
}
/// A simple data-holder for a method that needs to return multiple values.
class FinalConstVarOrType {
/// The 'final', 'const' or 'var' keyword, or `null` if none was given.
final Token keyword;
/// The type, or `null` if no type was specified.
final TypeAnnotation type;
/// Initialize a newly created holder with the given [keyword] and [type].
FinalConstVarOrType(this.keyword, this.type);
}
/// A simple data-holder for a method that needs to return multiple values.
class Modifiers {
/// The token representing the keyword 'abstract', or `null` if the keyword
/// was not found.
Token abstractKeyword;
/// The token representing the keyword 'const', or `null` if the keyword was
/// not found.
Token constKeyword;
/// The token representing the keyword 'covariant', or `null` if the keyword
/// was not found.
Token covariantKeyword;
/// The token representing the keyword 'external', or `null` if the keyword
/// was not found.
Token externalKeyword;
/// The token representing the keyword 'factory', or `null` if the keyword was
/// not found.
Token factoryKeyword;
/// The token representing the keyword 'final', or `null` if the keyword was
/// not found.
Token finalKeyword;
/// The token representing the keyword 'static', or `null` if the keyword was
/// not found.
Token staticKeyword;
/// The token representing the keyword 'var', or `null` if the keyword was not
/// found.
Token varKeyword;
@override
String toString() {
StringBuffer buffer = new StringBuffer();
bool needsSpace = _appendKeyword(buffer, false, abstractKeyword);
needsSpace = _appendKeyword(buffer, needsSpace, constKeyword);
needsSpace = _appendKeyword(buffer, needsSpace, externalKeyword);
needsSpace = _appendKeyword(buffer, needsSpace, factoryKeyword);
needsSpace = _appendKeyword(buffer, needsSpace, finalKeyword);
needsSpace = _appendKeyword(buffer, needsSpace, staticKeyword);
_appendKeyword(buffer, needsSpace, varKeyword);
return buffer.toString();
}
/// If the given [keyword] is not `null`, append it to the given [builder],
/// prefixing it with a space if [needsSpace] is `true`. Return `true` if
/// subsequent keywords need to be prefixed with a space.
bool _appendKeyword(StringBuffer buffer, bool needsSpace, Token keyword) {
if (keyword != null) {
if (needsSpace) {
buffer.writeCharCode(0x20);
}
buffer.write(keyword.lexeme);
return true;
}
return needsSpace;
}
}
/// A parser used to parse tokens into an AST structure.
class Parser {
static String ASYNC = Keyword.ASYNC.lexeme;
static String _AWAIT = Keyword.AWAIT.lexeme;
static String _HIDE = Keyword.HIDE.lexeme;
static String _SHOW = Keyword.SHOW.lexeme;
static String SYNC = Keyword.SYNC.lexeme;
static String _YIELD = Keyword.YIELD.lexeme;
static const int _MAX_TREE_DEPTH = 300;
/// A flag indicating whether the analyzer [Parser] factory method
/// will return a fasta based parser or an analyzer based parser.
static const bool useFasta =
const bool.fromEnvironment("useFastaParser", defaultValue: true);
/// The source being parsed.
final Source _source;
/// The error listener that will be informed of any errors that are found
/// during the parse.
final AnalysisErrorListener _errorListener;
/// An [_errorListener] lock, if more than `0`, then errors are not reported.
int _errorListenerLock = 0;
/// A flag indicating whether the parser should parse instance creation
/// expressions that lack either the `new` or `const` keyword.
bool _enableOptionalNewAndConst = true;
/// A flag indicating whether parser is to parse function bodies.
bool _parseFunctionBodies = true;
/// The next token to be parsed.
Token _currentToken;
/// The depth of the current AST. When this depth is too high, so we're at the
/// risk of overflowing the stack, we stop parsing and report an error.
int _treeDepth = 0;
/// A flag indicating whether the parser is currently in a function body
/// marked as being 'async'.
bool _inAsync = false;
/// A flag indicating whether the parser is currently in a function body
/// marked(by a star) as being a generator.
bool _inGenerator = false;
/// A flag indicating whether the parser is currently in the body of a loop.
bool _inLoop = false;
/// A flag indicating whether the parser is currently in a switch statement.
bool _inSwitch = false;
/// A flag indicating whether the parser is currently in a constructor field
/// initializer, with no intervening parentheses, braces, or brackets.
bool _inInitializer = false;
/// A flag indicating whether the parser is to parse generic method syntax.
@deprecated
bool parseGenericMethods = false;
bool allowNativeClause;
/// Initialize a newly created parser to parse tokens in the given [_source]
/// and to report any errors that are found to the given [_errorListener].
factory Parser(Source source, AnalysisErrorListener errorListener,
{bool useFasta}) {
if (useFasta ?? Parser.useFasta) {
return new _Parser2(source, errorListener, allowNativeClause: true);
} else {
return new Parser.withoutFasta(source, errorListener);
}
}
Parser.withoutFasta(this._source, this._errorListener);
/// Return the current token.
Token get currentToken => _currentToken;
/// Set the token with which the parse is to begin to the given [token].
void set currentToken(Token token) {
this._currentToken = token;
}
/// Return `true` if the parser is to parse asserts in the initializer list of
/// a constructor.
@deprecated
bool get enableAssertInitializer => true;
/// Set whether the parser is to parse asserts in the initializer list of a
/// constructor to match the given [enable] flag.
@deprecated
void set enableAssertInitializer(bool enable) {}
/// Enables or disables parsing of control flow collections.
void set enableControlFlowCollections(bool value) {
if (value) {
throw new UnimplementedError('control_flow_collections experiment'
' not supported by analyzer parser');
}
}
/// Enables or disables non-nullable by default.
void set enableNonNullable(bool value) {
if (value) {
throw new UnimplementedError(
'non-nullable experiment not supported by analyzer parser');
}
}
/// Return `true` if the parser should parse instance creation expressions
/// that lack either the `new` or `const` keyword.
bool get enableOptionalNewAndConst => _enableOptionalNewAndConst;
/// Set whether the parser should parse instance creation expressions that
/// lack either the `new` or `const` keyword.
void set enableOptionalNewAndConst(bool enable) {
_enableOptionalNewAndConst = enable;
}
/// Enables or disables parsing of set literals.
void set enableSetLiterals(bool value) {
// TODO(danrubel): Remove this method once the reference to this flag
// has been removed from dartfmt.
}
/// Enables or disables parsing of spread collections.
void set enableSpreadCollections(bool value) {
if (value) {
throw new UnimplementedError(
'spread_collections experiment not supported by analyzer parser');
}
}
/// Return `true` if the parser is to allow URI's in part-of directives.
@deprecated
bool get enableUriInPartOf => true;
/// Set whether the parser is to allow URI's in part-of directives to the
/// given [enable] flag.
@deprecated
void set enableUriInPartOf(bool enable) {}
/// Return `true` if the current token is the first token of a return type
/// that is followed by an identifier, possibly followed by a list of type
/// parameters, followed by a left-parenthesis. This is used by
/// [parseTypeAlias] to determine whether or not to parse a return type.
bool get hasReturnTypeInTypeAlias {
// TODO(brianwilkerson) This is too expensive as implemented and needs to be
// re-implemented or removed.
Token next = skipTypeAnnotation(_currentToken);
if (next == null) {
return false;
}
return _tokenMatchesIdentifier(next);
}
/// Set whether the parser is to parse the async support.
///
/// Support for removing the 'async' library has been removed.
@deprecated
void set parseAsync(bool parseAsync) {}
@deprecated
bool get parseConditionalDirectives => true;
@deprecated
void set parseConditionalDirectives(bool value) {}
/// Set whether parser is to parse function bodies.
void set parseFunctionBodies(bool parseFunctionBodies) {
this._parseFunctionBodies = parseFunctionBodies;
}
/// Return the content of a string with the given literal representation. The
/// [lexeme] is the literal representation of the string. The flag [isFirst]
/// is `true` if this is the first token in a string literal. The flag
/// [isLast] is `true` if this is the last token in a string literal.
String computeStringValue(String lexeme, bool isFirst, bool isLast) {
StringLexemeHelper helper = new StringLexemeHelper(lexeme, isFirst, isLast);
int start = helper.start;
int end = helper.end;
bool stringEndsAfterStart = end >= start;
assert(stringEndsAfterStart);
if (!stringEndsAfterStart) {
AnalysisEngine.instance.logger.logError(
"Internal error: computeStringValue($lexeme, $isFirst, $isLast)");
return "";
}
if (helper.isRaw) {
return lexeme.substring(start, end);
}
StringBuffer buffer = new StringBuffer();
int index = start;
while (index < end) {
index = _translateCharacter(buffer, lexeme, index);
}
return buffer.toString();
}
/// Return a synthetic identifier.
SimpleIdentifier createSyntheticIdentifier({bool isDeclaration: false}) {
Token syntheticToken;
if (_currentToken.type.isKeyword) {
// Consider current keyword token as an identifier.
// It is not always true, e.g. "^is T" where "^" is place the place for
// synthetic identifier. By creating SyntheticStringToken we can
// distinguish a real identifier from synthetic. In the code completion
// behavior will depend on a cursor position - before or on "is".
syntheticToken = _injectToken(new SyntheticStringToken(
TokenType.IDENTIFIER, _currentToken.lexeme, _currentToken.offset));
} else {
syntheticToken = _createSyntheticToken(TokenType.IDENTIFIER);
}
return astFactory.simpleIdentifier(syntheticToken,
isDeclaration: isDeclaration);
}
/// Return a synthetic string literal.
SimpleStringLiteral createSyntheticStringLiteral() => astFactory
.simpleStringLiteral(_createSyntheticToken(TokenType.STRING), "");
/// Advance to the next token in the token stream, making it the new current
/// token and return the token that was current before this method was
/// invoked.
Token getAndAdvance() {
Token token = _currentToken;
_currentToken = _currentToken.next;
return token;
}
/// Return `true` if the current token appears to be the beginning of a
/// function declaration.
bool isFunctionDeclaration() {
Keyword keyword = _currentToken.keyword;
Token afterReturnType = skipTypeWithoutFunction(_currentToken);
if (afterReturnType != null &&
_tokenMatchesKeyword(afterReturnType, Keyword.FUNCTION)) {
afterReturnType = skipGenericFunctionTypeAfterReturnType(afterReturnType);
}
if (afterReturnType == null) {
// There was no return type, but it is optional, so go back to where we
// started.
afterReturnType = _currentToken;
}
Token afterIdentifier = skipSimpleIdentifier(afterReturnType);
if (afterIdentifier == null) {
// It's possible that we parsed the function name as if it were a type
// name, so see whether it makes sense if we assume that there is no type.
afterIdentifier = skipSimpleIdentifier(_currentToken);
}
if (afterIdentifier == null) {
return false;
}
if (isFunctionExpression(afterIdentifier)) {
return true;
}
// It's possible that we have found a getter. While this isn't valid at this
// point we test for it in order to recover better.
if (keyword == Keyword.GET) {
Token afterName = skipSimpleIdentifier(_currentToken.next);
if (afterName == null) {
return false;
}
TokenType type = afterName.type;
return type == TokenType.FUNCTION || type == TokenType.OPEN_CURLY_BRACKET;
} else if (_tokenMatchesKeyword(afterReturnType, Keyword.GET)) {
Token afterName = skipSimpleIdentifier(afterReturnType.next);
if (afterName == null) {
return false;
}
TokenType type = afterName.type;
return type == TokenType.FUNCTION || type == TokenType.OPEN_CURLY_BRACKET;
}
return false;
}
/// Return `true` if the given [token] appears to be the beginning of a
/// function expression.
bool isFunctionExpression(Token token) {
// Function expressions aren't allowed in initializer lists.
if (_inInitializer) {
return false;
}
Token afterTypeParameters = _skipTypeParameterList(token);
if (afterTypeParameters == null) {
afterTypeParameters = token;
}
Token afterParameters = _skipFormalParameterList(afterTypeParameters);
if (afterParameters == null) {
return false;
}
if (afterParameters.matchesAny(
const <TokenType>[TokenType.OPEN_CURLY_BRACKET, TokenType.FUNCTION])) {
return true;
}
String lexeme = afterParameters.lexeme;
return lexeme == ASYNC || lexeme == SYNC;
}
/// Return `true` if the current token is the first token in an initialized
/// variable declaration rather than an expression. This method assumes that
/// we have already skipped past any metadata that might be associated with
/// the declaration.
///
/// initializedVariableDeclaration ::=
/// declaredIdentifier ('=' expression)? (',' initializedIdentifier)*
///
/// declaredIdentifier ::=
/// metadata finalConstVarOrType identifier
///
/// finalConstVarOrType ::=
/// 'final' type?
/// | 'const' type?
/// | 'var'
/// | type
///
/// type ::=
/// qualified typeArguments?
///
/// initializedIdentifier ::=
/// identifier ('=' expression)?
bool isInitializedVariableDeclaration() {
Keyword keyword = _currentToken.keyword;
if (keyword == Keyword.FINAL ||
keyword == Keyword.VAR ||
keyword == Keyword.VOID) {
// An expression cannot start with a keyword other than 'const',
// 'rethrow', or 'throw'.
return true;
}
if (keyword == Keyword.CONST) {
// Look to see whether we might be at the start of a list or map literal,
// otherwise this should be the start of a variable declaration.
return !_peek().matchesAny(const <TokenType>[
TokenType.LT,
TokenType.OPEN_CURLY_BRACKET,
TokenType.OPEN_SQUARE_BRACKET,
TokenType.INDEX
]);
}
bool allowAdditionalTokens = true;
// We know that we have an identifier, and need to see whether it might be
// a type name.
if (_currentToken.type != TokenType.IDENTIFIER) {
allowAdditionalTokens = false;
}
Token token = skipTypeName(_currentToken);
if (token == null) {
// There was no type name, so this can't be a declaration.
return false;
}
while (_atGenericFunctionTypeAfterReturnType(token)) {
token = skipGenericFunctionTypeAfterReturnType(token);
if (token == null) {
// There was no type name, so this can't be a declaration.
return false;
}
}
if (token.type != TokenType.IDENTIFIER) {
allowAdditionalTokens = false;
}
token = skipSimpleIdentifier(token);
if (token == null) {
return false;
}
TokenType type = token.type;
// Usual cases in valid code:
// String v = '';
// String v, v2;
// String v;
// for (String item in items) {}
if (type == TokenType.EQ ||
type == TokenType.COMMA ||
type == TokenType.SEMICOLON ||
token.keyword == Keyword.IN) {
return true;
}
// It is OK to parse as a variable declaration in these cases:
// String v }
// String v if (true) print('OK');
// String v { print(42); }
// ...but not in these cases:
// get getterName {
// String get getterName
if (allowAdditionalTokens) {
if (type == TokenType.CLOSE_CURLY_BRACKET ||
type.isKeyword ||
type == TokenType.IDENTIFIER ||
type == TokenType.OPEN_CURLY_BRACKET) {
return true;
}
}
return false;
}
/// Return `true` if the current token appears to be the beginning of a switch
/// member.
bool isSwitchMember() {
Token token = _currentToken;
while (_tokenMatches(token, TokenType.IDENTIFIER) &&
_tokenMatches(token.next, TokenType.COLON)) {
token = token.next.next;
}
Keyword keyword = token.keyword;
return keyword == Keyword.CASE || keyword == Keyword.DEFAULT;
}
/// Parse an additive expression. Return the additive expression that was
/// parsed.
///
/// additiveExpression ::=
/// multiplicativeExpression (additiveOperator multiplicativeExpression)*
/// | 'super' (additiveOperator multiplicativeExpression)+
Expression parseAdditiveExpression() {
Expression expression;
if (_currentToken.keyword == Keyword.SUPER &&
_currentToken.next.type.isAdditiveOperator) {
expression = astFactory.superExpression(getAndAdvance());
} else {
expression = parseMultiplicativeExpression();
}
while (_currentToken.type.isAdditiveOperator) {
expression = astFactory.binaryExpression(
expression, getAndAdvance(), parseMultiplicativeExpression());
}
return expression;
}
/// Parse an annotation. Return the annotation that was parsed.
///
/// This method assumes that the current token matches [TokenType.AT].
///
/// annotation ::=
/// '@' qualified ('.' identifier)? arguments?
Annotation parseAnnotation() {
Token atSign = getAndAdvance();
Identifier name = parsePrefixedIdentifier();
Token period = null;
SimpleIdentifier constructorName = null;
if (_matches(TokenType.PERIOD)) {
period = getAndAdvance();
constructorName = parseSimpleIdentifier();
}
ArgumentList arguments = null;
if (_matches(TokenType.OPEN_PAREN)) {
arguments = parseArgumentList();
}
return astFactory.annotation(
atSign, name, period, constructorName, arguments);
}
/// Parse an argument. Return the argument that was parsed.
///
/// argument ::=
/// namedArgument
/// | expression
///
/// namedArgument ::=
/// label expression
Expression parseArgument() {
// TODO(brianwilkerson) Consider returning a wrapper indicating whether the
// expression is a named expression in order to remove the 'is' check in
// 'parseArgumentList'.
//
// Both namedArgument and expression can start with an identifier, but only
// namedArgument can have an identifier followed by a colon.
//
if (_matchesIdentifier() && _tokenMatches(_peek(), TokenType.COLON)) {
return astFactory.namedExpression(parseLabel(), parseExpression2());
} else {
return parseExpression2();
}
}
/// Parse a list of arguments. Return the argument list that was parsed.
///
/// This method assumes that the current token matches [TokenType.OPEN_PAREN].
///
/// arguments ::=
/// '(' argumentList? ')'
///
/// argumentList ::=
/// namedArgument (',' namedArgument)*
/// | expressionList (',' namedArgument)*
ArgumentList parseArgumentList() {
Token leftParenthesis = getAndAdvance();
if (_matches(TokenType.CLOSE_PAREN)) {
return astFactory.argumentList(leftParenthesis, null, getAndAdvance());
}
/// Return `true` if the parser appears to be at the beginning of an
/// argument even though there was no comma. This is a special case of the
/// more general recovery technique described below.
bool isLikelyMissingComma() {
if (_matchesIdentifier() &&
_tokenMatches(_currentToken.next, TokenType.COLON) &&
leftParenthesis is BeginToken &&
leftParenthesis.endToken != null) {
_reportErrorForToken(
ParserErrorCode.EXPECTED_TOKEN, _currentToken.previous, [',']);
return true;
}
return false;
}
//
// Even though unnamed arguments must all appear before any named arguments,
// we allow them to appear in any order so that we can recover faster.
//
bool wasInInitializer = _inInitializer;
_inInitializer = false;
try {
Token previousStartOfArgument = _currentToken;
Expression argument = parseArgument();
List<Expression> arguments = <Expression>[argument];
bool foundNamedArgument = argument is NamedExpression;
bool generatedError = false;
while (_optional(TokenType.COMMA) ||
(isLikelyMissingComma() &&
previousStartOfArgument != _currentToken)) {
if (_matches(TokenType.CLOSE_PAREN)) {
break;
}
previousStartOfArgument = _currentToken;
argument = parseArgument();
arguments.add(argument);
if (argument is NamedExpression) {
foundNamedArgument = true;
} else if (foundNamedArgument) {
if (!generatedError) {
if (!argument.isSynthetic) {
// Report the error, once, but allow the arguments to be in any
// order in the AST.
_reportErrorForCurrentToken(
ParserErrorCode.POSITIONAL_AFTER_NAMED_ARGUMENT);
generatedError = true;
}
}
}
}
// Recovery: If the next token is not a right parenthesis, look at the
// left parenthesis to see whether there is a matching right parenthesis.
// If there is, then we're more likely missing a comma and should go back
// to parsing arguments.
Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
return astFactory.argumentList(
leftParenthesis, arguments, rightParenthesis);
} finally {
_inInitializer = wasInInitializer;
}
}
/// Parse an assert statement. Return the assert statement.
///
/// This method assumes that the current token matches `Keyword.ASSERT`.
///
/// assertStatement ::=
/// 'assert' '(' expression [',' expression] ')' ';'
AssertStatement parseAssertStatement() {
Token keyword = getAndAdvance();
Token leftParen = _expect(TokenType.OPEN_PAREN);
Expression expression = parseExpression2();
Token comma;
Expression message;
if (_matches(TokenType.COMMA)) {
comma = getAndAdvance();
if (_matches(TokenType.CLOSE_PAREN)) {
comma = null;
} else {
message = parseExpression2();
if (_matches(TokenType.COMMA)) {
getAndAdvance();
}
}
}
Token rightParen = _expect(TokenType.CLOSE_PAREN);
Token semicolon = _expect(TokenType.SEMICOLON);
// TODO(brianwilkerson) We should capture the trailing comma in the AST, but
// that would be a breaking change, so we drop it for now.
return astFactory.assertStatement(
keyword, leftParen, expression, comma, message, rightParen, semicolon);
}
/// Parse an assignable expression. The [primaryAllowed] is `true` if the
/// expression is allowed to be a primary without any assignable selector.
/// Return the assignable expression that was parsed.
///
/// assignableExpression ::=
/// primary (arguments* assignableSelector)+
/// | 'super' unconditionalAssignableSelector
/// | identifier
Expression parseAssignableExpression(bool primaryAllowed) {
//
// A primary expression can start with an identifier. We resolve the
// ambiguity by determining whether the primary consists of anything other
// than an identifier and/or is followed by an assignableSelector.
//
Expression expression = parsePrimaryExpression();
bool isOptional =
primaryAllowed || _isValidAssignableExpression(expression);
while (true) {
while (_isLikelyArgumentList()) {
TypeArgumentList typeArguments = _parseOptionalTypeArguments();
ArgumentList argumentList = parseArgumentList();
Expression currentExpression = expression;
if (currentExpression is SimpleIdentifier) {
expression = astFactory.methodInvocation(
null, null, currentExpression, typeArguments, argumentList);
} else if (currentExpression is PrefixedIdentifier) {
expression = astFactory.methodInvocation(
currentExpression.prefix,
currentExpression.period,
currentExpression.identifier,
typeArguments,
argumentList);
} else if (currentExpression is PropertyAccess) {
expression = astFactory.methodInvocation(
currentExpression.target,
currentExpression.operator,
currentExpression.propertyName,
typeArguments,
argumentList);
} else {
expression = astFactory.functionExpressionInvocation(
expression, typeArguments, argumentList);
}
if (!primaryAllowed) {
isOptional = false;
}
}
Expression selectorExpression = parseAssignableSelector(
expression, isOptional || (expression is PrefixedIdentifier));
if (identical(selectorExpression, expression)) {
return expression;
}
expression = selectorExpression;
isOptional = true;
}
}
/// Parse an assignable selector. The [prefix] is the expression preceding the
/// selector. The [optional] is `true` if the selector is optional. Return the
/// assignable selector that was parsed, or the original prefix if there was
/// no assignable selector. If [allowConditional] is false, then the '?.'
/// operator will still be parsed, but a parse error will be generated.
///
/// unconditionalAssignableSelector ::=
/// '[' expression ']'
/// | '.' identifier
///
/// assignableSelector ::=
/// unconditionalAssignableSelector
/// | '?.' identifier
Expression parseAssignableSelector(Expression prefix, bool optional,
{bool allowConditional: true}) {
TokenType type = _currentToken.type;
if (type == TokenType.OPEN_SQUARE_BRACKET) {
Token leftBracket = getAndAdvance();
bool wasInInitializer = _inInitializer;
_inInitializer = false;
try {
Expression index = parseExpression2();
Token rightBracket = _expect(TokenType.CLOSE_SQUARE_BRACKET);
return astFactory.indexExpressionForTarget(
prefix, leftBracket, index, rightBracket);
} finally {
_inInitializer = wasInInitializer;
}
} else {
bool isQuestionPeriod = type == TokenType.QUESTION_PERIOD;
if (type == TokenType.PERIOD || isQuestionPeriod) {
if (isQuestionPeriod && !allowConditional) {
_reportErrorForCurrentToken(
ParserErrorCode.INVALID_OPERATOR_FOR_SUPER,
[_currentToken.lexeme]);
}
Token operator = getAndAdvance();
return astFactory.propertyAccess(
prefix, operator, parseSimpleIdentifier());
} else if (type == TokenType.INDEX) {
_splitIndex();
Token leftBracket = getAndAdvance();
Expression index = parseSimpleIdentifier();
Token rightBracket = getAndAdvance();
return astFactory.indexExpressionForTarget(
prefix, leftBracket, index, rightBracket);
} else {
if (!optional) {
// Report the missing selector.
_reportErrorForCurrentToken(
ParserErrorCode.MISSING_ASSIGNABLE_SELECTOR);
}
return prefix;
}
}
}
/// Parse a await expression. Return the await expression that was parsed.
///
/// This method assumes that the current token matches `_AWAIT`.
///
/// awaitExpression ::=
/// 'await' unaryExpression
AwaitExpression parseAwaitExpression() {
Token awaitToken = getAndAdvance();
Expression expression = parseUnaryExpression();
return astFactory.awaitExpression(awaitToken, expression);
}
/// Parse a bitwise and expression. Return the bitwise and expression that was
/// parsed.
///
/// bitwiseAndExpression ::=
/// shiftExpression ('&' shiftExpression)*
/// | 'super' ('&' shiftExpression)+
Expression parseBitwiseAndExpression() {
Expression expression;
if (_currentToken.keyword == Keyword.SUPER &&
_currentToken.next.type == TokenType.AMPERSAND) {
expression = astFactory.superExpression(getAndAdvance());
} else {
expression = parseShiftExpression();
}
while (_currentToken.type == TokenType.AMPERSAND) {
expression = astFactory.binaryExpression(
expression, getAndAdvance(), parseShiftExpression());
}
return expression;
}
/// Parse a bitwise or expression. Return the bitwise or expression that was
/// parsed.
///
/// bitwiseOrExpression ::=
/// bitwiseXorExpression ('|' bitwiseXorExpression)*
/// | 'super' ('|' bitwiseXorExpression)+
Expression parseBitwiseOrExpression() {
Expression expression;
if (_currentToken.keyword == Keyword.SUPER &&
_currentToken.next.type == TokenType.BAR) {
expression = astFactory.superExpression(getAndAdvance());
} else {
expression = parseBitwiseXorExpression();
}
while (_currentToken.type == TokenType.BAR) {
expression = astFactory.binaryExpression(
expression, getAndAdvance(), parseBitwiseXorExpression());
}
return expression;
}
/// Parse a bitwise exclusive-or expression. Return the bitwise exclusive-or
/// expression that was parsed.
///
/// bitwiseXorExpression ::=
/// bitwiseAndExpression ('^' bitwiseAndExpression)*
/// | 'super' ('^' bitwiseAndExpression)+
Expression parseBitwiseXorExpression() {
Expression expression;
if (_currentToken.keyword == Keyword.SUPER &&
_currentToken.next.type == TokenType.CARET) {
expression = astFactory.superExpression(getAndAdvance());
} else {
expression = parseBitwiseAndExpression();
}
while (_currentToken.type == TokenType.CARET) {
expression = astFactory.binaryExpression(
expression, getAndAdvance(), parseBitwiseAndExpression());
}
return expression;
}
/// Parse a block. Return the block that was parsed.
///
/// This method assumes that the current token matches
/// [TokenType.OPEN_CURLY_BRACKET].
///
/// block ::=
/// '{' statements '}'
Block parseBlock() {
bool isEndOfBlock() {
TokenType type = _currentToken.type;
return type == TokenType.EOF || type == TokenType.CLOSE_CURLY_BRACKET;
}
Token leftBracket = getAndAdvance();
List<Statement> statements = <Statement>[];
Token statementStart = _currentToken;
while (!isEndOfBlock()) {
Statement statement = parseStatement2();
if (identical(_currentToken, statementStart)) {
// Ensure that we are making progress and report an error if we're not.
_reportErrorForToken(ParserErrorCode.UNEXPECTED_TOKEN, _currentToken,
[_currentToken.lexeme]);
_advance();
} else if (statement != null) {
statements.add(statement);
}
statementStart = _currentToken;
}
// Recovery: If the next token is not a right curly bracket, look at the
// left curly bracket to see whether there is a matching right bracket. If
// there is, then we're more likely missing a semi-colon and should go back
// to parsing statements.
Token rightBracket = _expect(TokenType.CLOSE_CURLY_BRACKET);
return astFactory.block(leftBracket, statements, rightBracket);
}
/// Parse a break statement. Return the break statement that was parsed.
///
/// This method assumes that the current token matches `Keyword.BREAK`.
///
/// breakStatement ::=
/// 'break' identifier? ';'
Statement parseBreakStatement() {
Token breakKeyword = getAndAdvance();
SimpleIdentifier label = null;
if (_matchesIdentifier()) {
label = _parseSimpleIdentifierUnchecked();
}
if (!_inLoop && !_inSwitch && label == null) {
_reportErrorForToken(ParserErrorCode.BREAK_OUTSIDE_OF_LOOP, breakKeyword);
}
Token semicolon = _expect(TokenType.SEMICOLON);
return astFactory.breakStatement(breakKeyword, label, semicolon);
}
/// Parse a cascade section. Return the expression representing the cascaded
/// method invocation.
///
/// This method assumes that the current token matches
/// `TokenType.PERIOD_PERIOD`.
///
/// cascadeSection ::=
/// '..' (cascadeSelector typeArguments? arguments*)
/// (assignableSelector typeArguments? arguments*)* cascadeAssignment?
///
/// cascadeSelector ::=
/// '[' expression ']'
/// | identifier
///
/// cascadeAssignment ::=
/// assignmentOperator expressionWithoutCascade
Expression parseCascadeSection() {
Token period = getAndAdvance();
Expression expression = null;
SimpleIdentifier functionName = null;
if (_matchesIdentifier()) {
functionName = _parseSimpleIdentifierUnchecked();
} else if (_currentToken.type == TokenType.OPEN_SQUARE_BRACKET) {
Token leftBracket = getAndAdvance();
bool wasInInitializer = _inInitializer;
_inInitializer = false;
try {
Expression index = parseExpression2();
Token rightBracket = _expect(TokenType.CLOSE_SQUARE_BRACKET);
expression = astFactory.indexExpressionForCascade(
period, leftBracket, index, rightBracket);
period = null;
} finally {
_inInitializer = wasInInitializer;
}
} else {
_reportErrorForToken(ParserErrorCode.MISSING_IDENTIFIER, _currentToken,
[_currentToken.lexeme]);
functionName = createSyntheticIdentifier();
}
assert((expression == null && functionName != null) ||
(expression != null && functionName == null));
if (_isLikelyArgumentList()) {
do {
TypeArgumentList typeArguments = _parseOptionalTypeArguments();
if (functionName != null) {
expression = astFactory.methodInvocation(expression, period,
functionName, typeArguments, parseArgumentList());
period = null;
functionName = null;
} else if (expression == null) {
// It should not be possible to get here.
expression = astFactory.methodInvocation(expression, period,
createSyntheticIdentifier(), typeArguments, parseArgumentList());
} else {
expression = astFactory.functionExpressionInvocation(
expression, typeArguments, parseArgumentList());
}
} while (_isLikelyArgumentList());
} else if (functionName != null) {
expression = astFactory.propertyAccess(expression, period, functionName);
period = null;
}
assert(expression != null);
bool progress = true;
while (progress) {
progress = false;
Expression selector = parseAssignableSelector(expression, true);
if (!identical(selector, expression)) {
expression = selector;
progress = true;
while (_isLikelyArgumentList()) {
TypeArgumentList typeArguments = _parseOptionalTypeArguments();
Expression currentExpression = expression;
if (currentExpression is PropertyAccess) {
expression = astFactory.methodInvocation(
currentExpression.target,
currentExpression.operator,
currentExpression.propertyName,
typeArguments,
parseArgumentList());
} else {
expression = astFactory.functionExpressionInvocation(
expression, typeArguments, parseArgumentList());
}
}
}
}
if (_currentToken.type.isAssignmentOperator) {
Token operator = getAndAdvance();
_ensureAssignable(expression);
expression = astFactory.assignmentExpression(
expression, operator, parseExpressionWithoutCascade());
}
return expression;
}
/// Parse a class declaration. The [commentAndMetadata] is the metadata to be
/// associated with the member. The [abstractKeyword] is the token for the
/// keyword 'abstract', or `null` if the keyword was not given. Return the
/// class declaration that was parsed.
///
/// This method assumes that the current token matches `Keyword.CLASS`.
///
/// classDeclaration ::=
/// metadata 'abstract'? 'class' name typeParameterList? (extendsClause withClause?)? implementsClause? '{' classMembers '}' |
/// metadata 'abstract'? 'class' mixinApplicationClass
CompilationUnitMember parseClassDeclaration(
CommentAndMetadata commentAndMetadata, Token abstractKeyword) {
//
// Parse the name and type parameters.
//
Token keyword = getAndAdvance();
SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
String className = name.name;
TypeParameterList typeParameters = null;
TokenType type = _currentToken.type;
if (type == TokenType.LT) {
typeParameters = parseTypeParameterList();
type = _currentToken.type;
}
//
// Check to see whether this might be a class type alias rather than a class
// declaration.
//
if (type == TokenType.EQ) {
return _parseClassTypeAliasAfterName(
commentAndMetadata, abstractKeyword, keyword, name, typeParameters);
}
//
// Parse the clauses. The parser accepts clauses in any order, but will
// generate errors if they are not in the order required by the
// specification.
//
ExtendsClause extendsClause = null;
WithClause withClause = null;
ImplementsClause implementsClause = null;
bool foundClause = true;
while (foundClause) {
Keyword keyword = _currentToken.keyword;
if (keyword == Keyword.EXTENDS) {
if (extendsClause == null) {
extendsClause = parseExtendsClause();
if (withClause != null) {
_reportErrorForToken(
ParserErrorCode.WITH_BEFORE_EXTENDS, withClause.withKeyword);
} else if (implementsClause != null) {
_reportErrorForToken(ParserErrorCode.IMPLEMENTS_BEFORE_EXTENDS,
implementsClause.implementsKeyword);
}
} else {
_reportErrorForToken(ParserErrorCode.MULTIPLE_EXTENDS_CLAUSES,
extendsClause.extendsKeyword);
parseExtendsClause();
}
} else if (keyword == Keyword.WITH) {
if (withClause == null) {
withClause = parseWithClause();
if (implementsClause != null) {
_reportErrorForToken(ParserErrorCode.IMPLEMENTS_BEFORE_WITH,
implementsClause.implementsKeyword);
}
} else {
_reportErrorForToken(
ParserErrorCode.MULTIPLE_WITH_CLAUSES, withClause.withKeyword);
parseWithClause();
// TODO(brianwilkerson) Should we merge the list of applied mixins
// into a single list?
}
} else if (keyword == Keyword.IMPLEMENTS) {
if (implementsClause == null) {
implementsClause = parseImplementsClause();
} else {
_reportErrorForToken(ParserErrorCode.MULTIPLE_IMPLEMENTS_CLAUSES,
implementsClause.implementsKeyword);
parseImplementsClause();
// TODO(brianwilkerson) Should we merge the list of implemented
// classes into a single list?
}
} else {
foundClause = false;
}
}
//
// Look for and skip over the extra-lingual 'native' specification.
//
NativeClause nativeClause = null;
if (_matchesKeyword(Keyword.NATIVE) &&
_tokenMatches(_peek(), TokenType.STRING)) {
nativeClause = _parseNativeClause();
}
//
// Parse the body of the class.
//
Token leftBracket = null;
List<ClassMember> members = null;
Token rightBracket = null;
if (_matches(TokenType.OPEN_CURLY_BRACKET)) {
leftBracket = getAndAdvance();
members = _parseClassMembers(className, _getEndToken(leftBracket));
rightBracket = _expect(TokenType.CLOSE_CURLY_BRACKET);
} else {
// Recovery: Check for an unmatched closing curly bracket and parse
// members until it is reached.
leftBracket = _createSyntheticToken(TokenType.OPEN_CURLY_BRACKET);
rightBracket = _createSyntheticToken(TokenType.CLOSE_CURLY_BRACKET);
_reportErrorForCurrentToken(ParserErrorCode.MISSING_CLASS_BODY);
}
ClassDeclaration classDeclaration = astFactory.classDeclaration(
commentAndMetadata.comment,
commentAndMetadata.metadata,
abstractKeyword,
keyword,
name,
typeParameters,
extendsClause,
withClause,
implementsClause,
leftBracket,
members,
rightBracket);
classDeclaration.nativeClause = nativeClause;
return classDeclaration;
}
/// Parse a class member. The [className] is the name of the class containing
/// the member being parsed. Return the class member that was parsed, or
/// `null` if what was found was not a valid class member.
///
/// classMemberDefinition ::=
/// declaration ';'
/// | methodSignature functionBody
ClassMember parseClassMember(String className) {
CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
Modifiers modifiers = parseModifiers();
Keyword keyword = _currentToken.keyword;
if (keyword == Keyword.VOID ||
_atGenericFunctionTypeAfterReturnType(_currentToken)) {
TypeAnnotation returnType;
if (keyword == Keyword.VOID) {
if (_atGenericFunctionTypeAfterReturnType(_peek())) {
returnType = parseTypeAnnotation(false);
} else {
returnType = astFactory.typeName(
astFactory.simpleIdentifier(getAndAdvance()), null);
}
} else {
returnType = parseTypeAnnotation(false);
}
keyword = _currentToken.keyword;
Token next = _peek();
bool isFollowedByIdentifier = _tokenMatchesIdentifier(next);
if (keyword == Keyword.GET && isFollowedByIdentifier) {
_validateModifiersForGetterOrSetterOrMethod(modifiers);
return parseGetter(commentAndMetadata, modifiers.externalKeyword,
modifiers.staticKeyword, returnType);
} else if (keyword == Keyword.SET && isFollowedByIdentifier) {
_validateModifiersForGetterOrSetterOrMethod(modifiers);
return parseSetter(commentAndMetadata, modifiers.externalKeyword,
modifiers.staticKeyword, returnType);
} else if (keyword == Keyword.OPERATOR &&
(_isOperator(next) || next.type == TokenType.EQ_EQ_EQ)) {
_validateModifiersForOperator(modifiers);
return _parseOperatorAfterKeyword(commentAndMetadata,
modifiers.externalKeyword, returnType, getAndAdvance());
} else if (_matchesIdentifier() &&
_peek().matchesAny(const <TokenType>[
TokenType.OPEN_PAREN,
TokenType.OPEN_CURLY_BRACKET,
TokenType.FUNCTION,
TokenType.LT
])) {
_validateModifiersForGetterOrSetterOrMethod(modifiers);
return _parseMethodDeclarationAfterReturnType(commentAndMetadata,
modifiers.externalKeyword, modifiers.staticKeyword, returnType);
} else if (_matchesIdentifier() &&
_peek().matchesAny(const <TokenType>[
TokenType.EQ,
TokenType.COMMA,
TokenType.SEMICOLON
])) {
return parseInitializedIdentifierList(
commentAndMetadata,
modifiers.staticKeyword,
modifiers.covariantKeyword,
_validateModifiersForField(modifiers),
returnType);
} else {
//
// We have found an error of some kind. Try to recover.
//
if (_isOperator(_currentToken)) {
//
// We appear to have found an operator declaration without the
// 'operator' keyword.
//
_validateModifiersForOperator(modifiers);
return parseOperator(
commentAndMetadata, modifiers.externalKeyword, returnType);
}
_reportErrorForToken(
ParserErrorCode.EXPECTED_EXECUTABLE, _currentToken);
return null;
}
}
Token next = _peek();
bool isFollowedByIdentifier = _tokenMatchesIdentifier(next);
if (keyword == Keyword.GET && isFollowedByIdentifier) {
_validateModifiersForGetterOrSetterOrMethod(modifiers);
return parseGetter(commentAndMetadata, modifiers.externalKeyword,
modifiers.staticKeyword, null);
} else if (keyword == Keyword.SET && isFollowedByIdentifier) {
_validateModifiersForGetterOrSetterOrMethod(modifiers);
return parseSetter(commentAndMetadata, modifiers.externalKeyword,
modifiers.staticKeyword, null);
} else if (keyword == Keyword.OPERATOR && _isOperator(next)) {
_validateModifiersForOperator(modifiers);
return _parseOperatorAfterKeyword(
commentAndMetadata, modifiers.externalKeyword, null, getAndAdvance());
} else if (!_matchesIdentifier()) {
//
// Recover from an error.
//
if (_matchesKeyword(Keyword.CLASS)) {
_reportErrorForCurrentToken(ParserErrorCode.CLASS_IN_CLASS);
// TODO(brianwilkerson) We don't currently have any way to capture the
// class that was parsed.
parseClassDeclaration(commentAndMetadata, null);
return null;
} else if (_matchesKeyword(Keyword.ABSTRACT) &&
_tokenMatchesKeyword(_peek(), Keyword.CLASS)) {
_reportErrorForToken(ParserErrorCode.CLASS_IN_CLASS, _peek());
// TODO(brianwilkerson) We don't currently have any way to capture the
// class that was parsed.
parseClassDeclaration(commentAndMetadata, getAndAdvance());
return null;
} else if (_matchesKeyword(Keyword.ENUM)) {
_reportErrorForToken(ParserErrorCode.ENUM_IN_CLASS, _peek());
// TODO(brianwilkerson) We don't currently have any way to capture the
// enum that was parsed.
parseEnumDeclaration(commentAndMetadata);
return null;
} else if (_isOperator(_currentToken)) {
//
// We appear to have found an operator declaration without the
// 'operator' keyword.
//
_validateModifiersForOperator(modifiers);
return parseOperator(
commentAndMetadata, modifiers.externalKeyword, null);
}
Token keyword = modifiers.varKeyword ??
modifiers.finalKeyword ??
modifiers.constKeyword;
if (keyword != null) {
//
// We appear to have found an incomplete field declaration.
//
_reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
VariableDeclaration variable = astFactory.variableDeclaration(
createSyntheticIdentifier(), null, null);
List<VariableDeclaration> variables = <VariableDeclaration>[variable];
return astFactory.fieldDeclaration2(
comment: commentAndMetadata.comment,
metadata: commentAndMetadata.metadata,
covariantKeyword: modifiers.covariantKeyword,
fieldList: astFactory.variableDeclarationList(
null, null, keyword, null, variables),
semicolon: _expect(TokenType.SEMICOLON));
}
_reportErrorForToken(
ParserErrorCode.EXPECTED_CLASS_MEMBER, _currentToken);
if (commentAndMetadata.comment != null ||
commentAndMetadata.hasMetadata) {
//
// We appear to have found an incomplete declaration at the end of the
// class. At this point it consists of a metadata, which we don't want
// to loose, so we'll treat it as a method declaration with a missing
// name, parameters and empty body.
//
return astFactory.methodDeclaration(
commentAndMetadata.comment,
commentAndMetadata.metadata,
null,
null,
null,
null,
null,
createSyntheticIdentifier(isDeclaration: true),
null,
astFactory.formalParameterList(
_createSyntheticToken(TokenType.OPEN_PAREN),
<FormalParameter>[],
null,
null,
_createSyntheticToken(TokenType.CLOSE_PAREN)),
astFactory
.emptyFunctionBody(_createSyntheticToken(TokenType.SEMICOLON)));
}
return null;
} else if (_tokenMatches(next, TokenType.PERIOD) &&
_tokenMatchesIdentifierOrKeyword(_peekAt(2)) &&
_tokenMatches(_peekAt(3), TokenType.OPEN_PAREN)) {
if (!_tokenMatchesIdentifier(_peekAt(2))) {
_reportErrorForToken(ParserErrorCode.INVALID_CONSTRUCTOR_NAME,
_peekAt(2), [_peekAt(2).lexeme]);
}
return _parseConstructor(
commentAndMetadata,
modifiers.externalKeyword,
_validateModifiersForConstructor(modifiers),
modifiers.factoryKeyword,
parseSimpleIdentifier(),
getAndAdvance(),
parseSimpleIdentifier(allowKeyword: true, isDeclaration: true),
parseFormalParameterList());
} else if (_tokenMatches(next, TokenType.OPEN_PAREN)) {
TypeName returnType = null;
SimpleIdentifier methodName = parseSimpleIdentifier(isDeclaration: true);
TypeParameterList typeParameters = null;
FormalParameterList parameters = parseFormalParameterList();
if (_matches(TokenType.COLON) ||
modifiers.factoryKeyword != null ||
methodName.name == className) {
return _parseConstructor(
commentAndMetadata,
modifiers.externalKeyword,
_validateModifiersForConstructor(modifiers),
modifiers.factoryKeyword,
astFactory.simpleIdentifier(methodName.token, isDeclaration: false),
null,
null,
parameters);
}
_validateModifiersForGetterOrSetterOrMethod(modifiers);
_validateFormalParameterList(parameters);
return _parseMethodDeclarationAfterParameters(
commentAndMetadata,
modifiers.externalKeyword,
modifiers.staticKeyword,
returnType,
methodName,
typeParameters,
parameters);
} else if (next.matchesAny(const <TokenType>[
TokenType.EQ,
TokenType.COMMA,
TokenType.SEMICOLON
])) {
if (modifiers.constKeyword == null &&
modifiers.finalKeyword == null &&
modifiers.varKeyword == null) {
_reportErrorForCurrentToken(
ParserErrorCode.MISSING_CONST_FINAL_VAR_OR_TYPE);
}
return parseInitializedIdentifierList(
commentAndMetadata,
modifiers.staticKeyword,
modifiers.covariantKeyword,
_validateModifiersForField(modifiers),
null);
} else if (keyword == Keyword.TYPEDEF) {
_reportErrorForCurrentToken(ParserErrorCode.TYPEDEF_IN_CLASS);
// TODO(brianwilkerson) We don't currently have any way to capture the
// function type alias that was parsed.
_parseFunctionTypeAlias(commentAndMetadata, getAndAdvance());
return null;
} else {
Token token = _skipTypeParameterList(_peek());
if (token != null && _tokenMatches(token, TokenType.OPEN_PAREN)) {
return _parseMethodDeclarationAfterReturnType(commentAndMetadata,
modifiers.externalKeyword, modifiers.staticKeyword, null);
}
}
TypeAnnotation type = _parseTypeAnnotationAfterIdentifier();
keyword = _currentToken.keyword;
next = _peek();
isFollowedByIdentifier = _tokenMatchesIdentifier(next);
if (keyword == Keyword.GET && isFollowedByIdentifier) {
_validateModifiersForGetterOrSetterOrMethod(modifiers);
return parseGetter(commentAndMetadata, modifiers.externalKeyword,
modifiers.staticKeyword, type);
} else if (keyword == Keyword.SET && isFollowedByIdentifier) {
_validateModifiersForGetterOrSetterOrMethod(modifiers);
return parseSetter(commentAndMetadata, modifiers.externalKeyword,
modifiers.staticKeyword, type);
} else if (keyword == Keyword.OPERATOR && _isOperator(next)) {
_validateModifiersForOperator(modifiers);
return _parseOperatorAfterKeyword(
commentAndMetadata, modifiers.externalKeyword, type, getAndAdvance());
} else if (!_matchesIdentifier()) {
if (_matches(TokenType.CLOSE_CURLY_BRACKET)) {
//
// We appear to have found an incomplete declaration at the end of the
// class. At this point it consists of a type name, so we'll treat it as
// a field declaration with a missing field name and semicolon.
//
return parseInitializedIdentifierList(
commentAndMetadata,
modifiers.staticKeyword,
modifiers.covariantKeyword,
_validateModifiersForField(modifiers),
type);
}
if (_isOperator(_currentToken)) {
//
// We appear to have found an operator declaration without the
// 'operator' keyword.
//
_validateModifiersForOperator(modifiers);
return parseOperator(
commentAndMetadata, modifiers.externalKeyword, type);
}
//
// We appear to have found an incomplete declaration before another
// declaration. At this point it consists of a type name, so we'll treat
// it as a field declaration with a missing field name and semicolon.
//
_reportErrorForToken(
ParserErrorCode.EXPECTED_CLASS_MEMBER, _currentToken);
try {
_lockErrorListener();
return parseInitializedIdentifierList(
commentAndMetadata,
modifiers.staticKeyword,
modifiers.covariantKeyword,
_validateModifiersForField(modifiers),
type);
} finally {
_unlockErrorListener();
}
} else if (_tokenMatches(next, TokenType.OPEN_PAREN)) {
SimpleIdentifier methodName =
_parseSimpleIdentifierUnchecked(isDeclaration: true);
TypeParameterList typeParameters = null;
FormalParameterList parameters = parseFormalParameterList();
if (methodName.name == className) {
_reportErrorForNode(ParserErrorCode.CONSTRUCTOR_WITH_RETURN_TYPE, type);
return _parseConstructor(
commentAndMetadata,
modifiers.externalKeyword,
_validateModifiersForConstructor(modifiers),
modifiers.factoryKeyword,
astFactory.simpleIdentifier(methodName.token, isDeclaration: true),
null,
null,
parameters);
}
_validateModifiersForGetterOrSetterOrMethod(modifiers);
_validateFormalParameterList(parameters);
return _parseMethodDeclarationAfterParameters(
commentAndMetadata,
modifiers.externalKeyword,
modifiers.staticKeyword,
type,
methodName,
typeParameters,
parameters);
} else if (_tokenMatches(next, TokenType.LT)) {
return _parseMethodDeclarationAfterReturnType(commentAndMetadata,
modifiers.externalKeyword, modifiers.staticKeyword, type);
} else if (_tokenMatches(next, TokenType.OPEN_CURLY_BRACKET)) {
// We have found "TypeName identifier {", and are guessing that this is a
// getter without the keyword 'get'.
_validateModifiersForGetterOrSetterOrMethod(modifiers);
_reportErrorForCurrentToken(ParserErrorCode.MISSING_GET);
_currentToken = _injectToken(
new SyntheticKeywordToken(Keyword.GET, _currentToken.offset));
return parseGetter(commentAndMetadata, modifiers.externalKeyword,
modifiers.staticKeyword, type);
}
return parseInitializedIdentifierList(
commentAndMetadata,
modifiers.staticKeyword,
modifiers.covariantKeyword,
_validateModifiersForField(modifiers),
type);
}
/// Parse a single combinator. Return the combinator that was parsed, or
/// `null` if no combinator is found.
///
/// combinator ::=
/// 'show' identifier (',' identifier)*
/// | 'hide' identifier (',' identifier)*
Combinator parseCombinator() {
if (_matchesKeyword(Keyword.SHOW)) {
return astFactory.showCombinator(getAndAdvance(), parseIdentifierList());
} else if (_matchesKeyword(Keyword.HIDE)) {
return astFactory.hideCombinator(getAndAdvance(), parseIdentifierList());
}
return null;
}
/// Parse a list of combinators in a directive. Return the combinators that
/// were parsed, or `null` if there are no combinators.
///
/// combinator ::=
/// 'show' identifier (',' identifier)*
/// | 'hide' identifier (',' identifier)*
List<Combinator> parseCombinators() {
List<Combinator> combinators = null;
while (true) {
Combinator combinator = parseCombinator();
if (combinator == null) {
break;
}
combinators ??= <Combinator>[];
combinators.add(combinator);
}
return combinators;
}
/// Parse the documentation comment and metadata preceding a declaration. This
/// method allows any number of documentation comments to occur before, after
/// or between the metadata, but only returns the last (right-most)
/// documentation comment that is found. Return the documentation comment and
/// metadata that were parsed.
///
/// metadata ::=
/// annotation*
CommentAndMetadata parseCommentAndMetadata() {
// TODO(brianwilkerson) Consider making the creation of documentation
// comments be lazy.
List<DocumentationCommentToken> tokens = parseDocumentationCommentTokens();
List<Annotation> metadata = null;
while (_matches(TokenType.AT)) {
metadata ??= <Annotation>[];
metadata.add(parseAnnotation());
List<DocumentationCommentToken> optionalTokens =
parseDocumentationCommentTokens();
if (optionalTokens != null) {
tokens = optionalTokens;
}
}
return new CommentAndMetadata(parseDocumentationComment(tokens), metadata);
}
/// Parse a comment reference from the source between square brackets. The
/// [referenceSource] is the source occurring between the square brackets
/// within a documentation comment. The [sourceOffset] is the offset of the
/// first character of the reference source. Return the comment reference that
/// was parsed, or `null` if no reference could be found.
///
/// commentReference ::=
/// 'new'? prefixedIdentifier
CommentReference parseCommentReference(
String referenceSource, int sourceOffset) {
// TODO(brianwilkerson) The errors are not getting the right offset/length
// and are being duplicated.
try {
BooleanErrorListener listener = new BooleanErrorListener();
Scanner scanner = new Scanner(
null, new SubSequenceReader(referenceSource, sourceOffset), listener);
scanner.setSourceStart(1, 1);
Token firstToken = scanner.tokenize();
if (listener.errorReported) {
return null;
}
if (firstToken.type == TokenType.EOF) {
Token syntheticToken =
new SyntheticStringToken(TokenType.IDENTIFIER, "", sourceOffset);
syntheticToken.setNext(firstToken);
return astFactory.commentReference(
null, astFactory.simpleIdentifier(syntheticToken));
}
Token newKeyword = null;
if (_tokenMatchesKeyword(firstToken, Keyword.NEW)) {
newKeyword = firstToken;
firstToken = firstToken.next;
}
if (firstToken.isUserDefinableOperator) {
if (firstToken.next.type != TokenType.EOF) {
return null;
}
Identifier identifier = astFactory.simpleIdentifier(firstToken);
return astFactory.commentReference(null, identifier);
} else if (_tokenMatchesKeyword(firstToken, Keyword.OPERATOR)) {
Token secondToken = firstToken.next;
if (secondToken.isUserDefinableOperator) {
if (secondToken.next.type != TokenType.EOF) {
return null;
}
Identifier identifier = astFactory.simpleIdentifier(secondToken);
return astFactory.commentReference(null, identifier);
}
return null;
} else if (_tokenMatchesIdentifier(firstToken)) {
Token secondToken = firstToken.next;
Token thirdToken = secondToken.next;
Token nextToken;
Identifier identifier;
if (_tokenMatches(secondToken, TokenType.PERIOD)) {
if (thirdToken.isUserDefinableOperator) {
identifier = astFactory.prefixedIdentifier(
astFactory.simpleIdentifier(firstToken),
secondToken,
astFactory.simpleIdentifier(thirdToken));
nextToken = thirdToken.next;
} else if (_tokenMatchesKeyword(thirdToken, Keyword.OPERATOR)) {
Token fourthToken = thirdToken.next;
if (fourthToken.isUserDefinableOperator) {
identifier = astFactory.prefixedIdentifier(
astFactory.simpleIdentifier(firstToken),
secondToken,
astFactory.simpleIdentifier(fourthToken));
nextToken = fourthToken.next;
} else {
return null;
}
} else if (_tokenMatchesIdentifier(thirdToken)) {
identifier = astFactory.prefixedIdentifier(
astFactory.simpleIdentifier(firstToken),
secondToken,
astFactory.simpleIdentifier(thirdToken));
nextToken = thirdToken.next;
}
} else {
identifier = astFactory.simpleIdentifier(firstToken);
nextToken = firstToken.next;
}
if (nextToken.type != TokenType.EOF) {
return null;
}
return astFactory.commentReference(newKeyword, identifier);
} else {
Keyword keyword = firstToken.keyword;
if (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.
return null;
}
}
} catch (exception) {
// Ignored because we assume that it wasn't a real comment reference.
}
return null;
}
/// Parse all of the comment references occurring in the given array of
/// documentation comments. The [tokens] are the comment tokens representing
/// the documentation comments to be parsed. Return the comment references
/// that were parsed.
///
/// commentReference ::=
/// '[' 'new'? qualified ']' libraryReference?
///
/// libraryReference ::=
/// '(' stringLiteral ')'
List<CommentReference> parseCommentReferences(
List<DocumentationCommentToken> tokens) {
List<CommentReference> references = <CommentReference>[];
bool isInGitHubCodeBlock = false;
for (DocumentationCommentToken token in tokens) {
String comment = token.lexeme;
// Skip GitHub code blocks.
// https://help.github.com/articles/creating-and-highlighting-code-blocks/
if (tokens.length != 1) {
if (comment.indexOf('```') != -1) {
isInGitHubCodeBlock = !isInGitHubCodeBlock;
}
if (isInGitHubCodeBlock) {
continue;
}
}
// Remove GitHub include code.
comment = _removeGitHubInlineCode(comment);
// Find references.
int length = comment.length;
List<List<int>> codeBlockRanges = _getCodeBlockRanges(comment);
int leftIndex = comment.indexOf('[');
while (leftIndex >= 0 && leftIndex + 1 < length) {
List<int> range = _findRange(codeBlockRanges, leftIndex);
if (range == null) {
int nameOffset = token.offset + leftIndex + 1;
int rightIndex = comment.indexOf(']', leftIndex);
if (rightIndex >= 0) {
int firstChar = comment.codeUnitAt(leftIndex + 1);
if (firstChar != 0x27 && firstChar != 0x22) {
if (_isLinkText(comment, rightIndex)) {
// TODO(brianwilkerson) Handle the case where there's a library
// URI in the link text.
} else {
CommentReference reference = parseCommentReference(
comment.substring(leftIndex + 1, rightIndex), nameOffset);
if (reference != null) {
references.add(reference);
}
}
}
} else {
// terminating ']' is not typed yet
int charAfterLeft = comment.codeUnitAt(leftIndex + 1);
Token nameToken;
if (Character.isLetterOrDigit(charAfterLeft)) {
int nameEnd = StringUtilities.indexOfFirstNotLetterDigit(
comment, leftIndex + 1);
String name = comment.substring(leftIndex + 1, nameEnd);
nameToken =
new StringToken(TokenType.IDENTIFIER, name, nameOffset);
} else {
nameToken = new SyntheticStringToken(
TokenType.IDENTIFIER, '', nameOffset);
}
nameToken.setNext(new Token.eof(nameToken.end));
references.add(astFactory.commentReference(
null, astFactory.simpleIdentifier(nameToken)));
// next character
rightIndex = leftIndex + 1;
}
leftIndex = comment.indexOf('[', rightIndex);
} else {
leftIndex = comment.indexOf('[', range[1]);
}
}
}
return references;
}
/// Parse a compilation unit, starting with the given [token]. Return the
/// compilation unit that was parsed.
CompilationUnit parseCompilationUnit(Token token) {
_currentToken = token;
return parseCompilationUnit2();
}
/// Parse a compilation unit. Return the compilation unit that was parsed.
///
/// Specified:
///
/// compilationUnit ::=
/// scriptTag? directive* topLevelDeclaration*
///
/// Actual:
///
/// compilationUnit ::=
/// scriptTag? topLevelElement*
///
/// topLevelElement ::=
/// directive
/// | topLevelDeclaration
CompilationUnit parseCompilationUnit2() {
Token firstToken = _currentToken;
ScriptTag scriptTag = null;
if (_matches(TokenType.SCRIPT_TAG)) {
scriptTag = astFactory.scriptTag(getAndAdvance());
}
//
// Even though all directives must appear before declarations and must occur
// in a given order, we allow directives and declarations to occur in any
// order so that we can recover better.
//
bool libraryDirectiveFound = false;
bool partOfDirectiveFound = false;
bool partDirectiveFound = false;
bool directiveFoundAfterDeclaration = false;
List<Directive> directives = <Directive>[];
List<CompilationUnitMember> declarations = <CompilationUnitMember>[];
Token memberStart = _currentToken;
TokenType type = _currentToken.type;
while (type != TokenType.EOF) {
CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
Keyword keyword = _currentToken.keyword;
TokenType nextType = _currentToken.next.type;
if ((keyword == Keyword.IMPORT ||
keyword == Keyword.EXPORT ||
keyword == Keyword.LIBRARY ||
keyword == Keyword.PART) &&
nextType != TokenType.PERIOD &&
nextType != TokenType.LT &&
nextType != TokenType.OPEN_PAREN) {
Directive parseDirective() {
if (keyword == Keyword.IMPORT) {
if (partDirectiveFound) {
_reportErrorForCurrentToken(
ParserErrorCode.IMPORT_DIRECTIVE_AFTER_PART_DIRECTIVE);
}
return parseImportDirective(commentAndMetadata);
} else if (keyword == Keyword.EXPORT) {
if (partDirectiveFound) {
_reportErrorForCurrentToken(
ParserErrorCode.EXPORT_DIRECTIVE_AFTER_PART_DIRECTIVE);
}
return parseExportDirective(commentAndMetadata);
} else if (keyword == Keyword.LIBRARY) {
if (libraryDirectiveFound) {
_reportErrorForCurrentToken(
ParserErrorCode.MULTIPLE_LIBRARY_DIRECTIVES);
} else {
if (directives.length > 0) {
_reportErrorForCurrentToken(
ParserErrorCode.LIBRARY_DIRECTIVE_NOT_FIRST);
}
libraryDirectiveFound = true;
}
return parseLibraryDirective(commentAndMetadata);
} else if (keyword == Keyword.PART) {
if (_tokenMatchesKeyword(_peek(), Keyword.OF)) {
partOfDirectiveFound = true;
return _parsePartOfDirective(commentAndMetadata);
} else {
partDirectiveFound = true;
return _parsePartDirective(commentAndMetadata);
}
} else {
// Internal error: this method should not have been invoked if the
// current token was something other than one of the above.
throw new StateError(
"parseDirective invoked in an invalid state (currentToken = $_currentToken)");
}
}
Directive directive = parseDirective();
if (declarations.length > 0 && !directiveFoundAfterDeclaration) {
_reportErrorForToken(ParserErrorCode.DIRECTIVE_AFTER_DECLARATION,
directive.beginToken);
directiveFoundAfterDeclaration = true;
}
directives.add(directive);
} else if (type == TokenType.SEMICOLON) {
// TODO(brianwilkerson) Consider moving this error detection into
// _parseCompilationUnitMember (in the places where EXPECTED_EXECUTABLE
// is being generated).
_reportErrorForToken(ParserErrorCode.UNEXPECTED_TOKEN, _currentToken,
[_currentToken.lexeme]);
_advance();
} else {
CompilationUnitMember member;
try {
member = parseCompilationUnitMember(commentAndMetadata);
} on _TooDeepTreeError {
_reportErrorForToken(ParserErrorCode.STACK_OVERFLOW, _currentToken);
Token eof = new Token.eof(0);
return astFactory.compilationUnit(eof, null, null, null, eof);
}
if (member != null) {
declarations.add(member);
}
}
if (identical(_currentToken, memberStart)) {
_reportErrorForToken(ParserErrorCode.UNEXPECTED_TOKEN, _currentToken,
[_currentToken.lexeme]);
_advance();
while (!_matches(TokenType.EOF) &&
!_couldBeStartOfCompilationUnitMember()) {
_advance();
}
}
memberStart = _currentToken;
type = _currentToken.type;
}
if (partOfDirectiveFound && directives.length > 1) {
// TODO(brianwilkerson) Improve error reporting when both a library and
// part-of directive are found.
// if (libraryDirectiveFound) {
// int directiveCount = directives.length;
// for (int i = 0; i < directiveCount; i++) {
// Directive directive = directives[i];
// if (directive is PartOfDirective) {
// _reportErrorForToken(
// ParserErrorCode.PART_OF_IN_LIBRARY, directive.partKeyword);
// }
// }
// } else {
bool firstPartOf = true;
int directiveCount = directives.length;
for (int i = 0; i < directiveCount; i++) {
Directive directive = directives[i];
if (directive is PartOfDirective) {
if (firstPartOf) {
firstPartOf = false;
} else {
_reportErrorForToken(ParserErrorCode.MULTIPLE_PART_OF_DIRECTIVES,
directive.partKeyword);
}
} else {
_reportErrorForToken(ParserErrorCode.NON_PART_OF_DIRECTIVE_IN_PART,
directives[i].keyword);
}
// }
}
}
return astFactory.compilationUnit(
firstToken, scriptTag, directives, declarations, _currentToken);
}
/// Parse a compilation unit member. The [commentAndMetadata] is the metadata
/// to be associated with the member. Return the compilation unit member that
/// was parsed, or `null` if what was parsed could not be represented as a
/// compilation unit member.
///
/// compilationUnitMember ::=
/// classDefinition
/// | functionTypeAlias
/// | external functionSignature
/// | external getterSignature
/// | external setterSignature
/// | functionSignature functionBody
/// | returnType? getOrSet identifier formalParameterList functionBody
/// | (final | const) type? staticFinalDeclarationList ';'
/// | variableDeclaration ';'
CompilationUnitMember parseCompilationUnitMember(
CommentAndMetadata commentAndMetadata) {
Modifiers modifiers = parseModifiers();
Keyword keyword = _currentToken.keyword;
if (keyword == Keyword.CLASS) {
return parseClassDeclaration(
commentAndMetadata, _validateModifiersForClass(modifiers));
}
Token next = _peek();
TokenType nextType = next.type;
if (keyword == Keyword.TYPEDEF &&
nextType != TokenType.PERIOD &&
nextType != TokenType.LT &&
nextType != TokenType.OPEN_PAREN) {
_validateModifiersForTypedef(modifiers);
return parseTypeAlias(commentAndMetadata);
} else if (keyword == Keyword.ENUM) {
_validateModifiersForEnum(modifiers);
return parseEnumDeclaration(commentAndMetadata);
} else if (keyword == Keyword.VOID ||
_atGenericFunctionTypeAfterReturnType(_currentToken)) {
TypeAnnotation returnType;
if (keyword == Keyword.VOID) {
if (_atGenericFunctionTypeAfterReturnType(next)) {
returnType = parseTypeAnnotation(false);
} else {
returnType = astFactory.typeName(
astFactory.simpleIdentifier(getAndAdvance()), null);
}
} else {
returnType = parseTypeAnnotation(false);
}
keyword = _currentToken.keyword;
next = _peek();
if ((keyword == Keyword.GET || keyword == Keyword.SET) &&
_tokenMatchesIdentifier(next)) {
_validateModifiersForTopLevelFunction(modifiers);
return parseFunctionDeclaration(
commentAndMetadata, modifiers.externalKeyword, returnType);
} else if (keyword == Keyword.OPERATOR && _isOperator(next)) {
_reportErrorForToken(ParserErrorCode.TOP_LEVEL_OPERATOR, _currentToken);
return _convertToFunctionDeclaration(_parseOperatorAfterKeyword(
commentAndMetadata,
modifiers.externalKeyword,
returnType,
getAndAdvance()));
} else if (_matchesIdentifier() &&
next.matchesAny(const <TokenType>[
TokenType.OPEN_PAREN,
TokenType.OPEN_CURLY_BRACKET,
TokenType.FUNCTION,
TokenType.LT
])) {
_validateModifiersForTopLevelFunction(modifiers);
return parseFunctionDeclaration(
commentAndMetadata, modifiers.externalKeyword, returnType);
} else if (_matchesIdentifier() &&
next.matchesAny(const <TokenType>[
TokenType.EQ,
TokenType.COMMA,
TokenType.SEMICOLON
])) {
return astFactory.topLevelVariableDeclaration(
commentAndMetadata.comment,
commentAndMetadata.metadata,
parseVariableDeclarationListAfterType(null,
_validateModifiersForTopLevelVariable(modifiers), returnType),
_expect(TokenType.SEMICOLON));
} else {
//
// We have found an error of some kind. Try to recover.
//
_reportErrorForToken(
ParserErrorCode.EXPECTED_EXECUTABLE, _currentToken);
return null;
}
} else if ((keyword == Keyword.GET || keyword == Keyword.SET) &&
_tokenMatchesIdentifier(next)) {
_validateModifiersForTopLevelFunction(modifiers);
return parseFunctionDeclaration(
commentAndMetadata, modifiers.externalKeyword, null);
} else if (keyword == Keyword.OPERATOR && _isOperator(next)) {
_reportErrorForToken(ParserErrorCode.TOP_LEVEL_OPERATOR, _currentToken);
return _convertToFunctionDeclaration(_parseOperatorAfterKeyword(
commentAndMetadata,
modifiers.externalKeyword,
null,
getAndAdvance()));
} else if (!_matchesIdentifier()) {
Token keyword = modifiers.varKeyword;
if (keyword == null) {
keyword = modifiers.finalKeyword;
}
if (keyword == null) {
keyword = modifiers.constKeyword;
}
if (keyword != null) {
//
// We appear to have found an incomplete top-level variable declaration.
//
_reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
VariableDeclaration variable = astFactory.variableDeclaration(
createSyntheticIdentifier(), null, null);
List<VariableDeclaration> variables = <VariableDeclaration>[variable];
return astFactory.topLevelVariableDeclaration(
commentAndMetadata.comment,
commentAndMetadata.metadata,
astFactory.variableDeclarationList(
null, null, keyword, null, variables),
_expect(TokenType.SEMICOLON));
}
_reportErrorForToken(ParserErrorCode.EXPECTED_EXECUTABLE, _currentToken);
return null;
} else if (_isPeekGenericTypeParametersAndOpenParen()) {
return parseFunctionDeclaration(
commentAndMetadata, modifiers.externalKeyword, null);
} else if (_tokenMatches(next, TokenType.OPEN_PAREN)) {
TypeName returnType = null;
_validateModifiersForTopLevelFunction(modifiers);
return parseFunctionDeclaration(
commentAndMetadata, modifiers.externalKeyword, returnType);
} else if (next.matchesAny(const <TokenType>[
TokenType.EQ,
TokenType.COMMA,
TokenType.SEMICOLON
])) {
if (modifiers.constKeyword == null &&
modifiers.finalKeyword == null &&
modifiers.varKeyword == null) {
_reportErrorForCurrentToken(
ParserErrorCode.MISSING_CONST_FINAL_VAR_OR_TYPE);
}
return astFactory.topLevelVariableDeclaration(
commentAndMetadata.comment,
commentAndMetadata.metadata,
parseVariableDeclarationListAfterType(
null, _validateModifiersForTopLevelVariable(modifiers), null),
_expect(TokenType.SEMICOLON));
}
TypeAnnotation returnType = parseTypeAnnotation(false);
keyword = _currentToken.keyword;
next = _peek();
if ((keyword == Keyword.GET || keyword == Keyword.SET) &&
_tokenMatchesIdentifier(next)) {
_validateModifiersForTopLevelFunction(modifiers);
return parseFunctionDeclaration(
commentAndMetadata, modifiers.externalKeyword, returnType);
} else if (keyword == Keyword.OPERATOR && _isOperator(next)) {
_reportErrorForToken(ParserErrorCode.TOP_LEVEL_OPERATOR, _currentToken);
return _convertToFunctionDeclaration(_parseOperatorAfterKeyword(
commentAndMetadata,
modifiers.externalKeyword,
returnType,
getAndAdvance()));
} else if (_matches(TokenType.AT)) {
return astFactory.topLevelVariableDeclaration(
commentAndMetadata.comment,
commentAndMetadata.metadata,
parseVariableDeclarationListAfterType(null,
_validateModifiersForTopLevelVariable(modifiers), returnType),
_expect(TokenType.SEMICOLON));
} else if (!_matchesIdentifier()) {
// TODO(brianwilkerson) Generalize this error. We could also be parsing a
// top-level variable at this point.
_reportErrorForToken(ParserErrorCode.EXPECTED_EXECUTABLE, _currentToken);
Token semicolon;
if (_matches(TokenType.SEMICOLON)) {
semicolon = getAndAdvance();
} else {
semicolon = _createSyntheticToken(TokenType.SEMICOLON);
}
VariableDeclaration variable = astFactory.variableDeclaration(
createSyntheticIdentifier(), null, null);
List<VariableDeclaration> variables = <VariableDeclaration>[variable];
return astFactory.topLevelVariableDeclaration(
commentAndMetadata.comment,
commentAndMetadata.metadata,
astFactory.variableDeclarationList(
null, null, null, returnType, variables),
semicolon);
} else if (next.matchesAny(const <TokenType>[
TokenType.OPEN_PAREN,
TokenType.FUNCTION,
TokenType.OPEN_CURLY_BRACKET,
TokenType.LT
])) {
_validateModifiersForTopLevelFunction(modifiers);
return parseFunctionDeclaration(
commentAndMetadata, modifiers.externalKeyword, returnType);
}
return astFactory.topLevelVariableDeclaration(
commentAndMetadata.comment,
commentAndMetadata.metadata,
parseVariableDeclarationListAfterType(
null, _validateModifiersForTopLevelVariable(modifiers), returnType),
_expect(TokenType.SEMICOLON));
}
/// Parse a conditional expression. Return the conditional expression that was
/// parsed.
///
/// conditionalExpression ::=
/// ifNullExpression ('?' expressionWithoutCascade ':' expressionWithoutCascade)?
Expression parseConditionalExpression() {
Expression condition = parseIfNullExpression();
if (_currentToken.type != TokenType.QUESTION) {
return condition;
}
Token question = getAndAdvance();
Expression thenExpression = parseExpressionWithoutCascade();
Token colon = _expect(TokenType.COLON);
Expression elseExpression = parseExpressionWithoutCascade();
return astFactory.conditionalExpression(
condition, question, thenExpression, colon, elseExpression);
}
/// Parse a configuration in either an import or export directive.
///
/// This method assumes that the current token matches `Keyword.IF`.
///
/// configuration ::=
/// 'if' '(' test ')' uri
///
/// test ::=
/// dottedName ('==' stringLiteral)?
///
/// dottedName ::=
/// identifier ('.' identifier)*
Configuration parseConfiguration() {
Token ifKeyword = getAndAdvance();
Token leftParenthesis = _expect(TokenType.OPEN_PAREN);
DottedName name = parseDottedName();
Token equalToken = null;
StringLiteral value = null;
if (_matches(TokenType.EQ_EQ)) {
equalToken = getAndAdvance();
value = parseStringLiteral();
if (value is StringInterpolation) {
_reportErrorForNode(
ParserErrorCode.INVALID_LITERAL_IN_CONFIGURATION, value);
}
}
Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
StringLiteral libraryUri = _parseUri();
return astFactory.configuration(ifKeyword, leftParenthesis, name,
equalToken, value, rightParenthesis, libraryUri);
}
/// Parse a const expression. Return the const expression that was parsed.
///
/// This method assumes that the current token matches `Keyword.CONST`.
///
/// constExpression ::=
/// instanceCreationExpression
/// | listLiteral
/// | mapLiteral
Expression parseConstExpression() {
Token keyword = getAndAdvance();
TokenType type = _currentToken.type;
if (type == TokenType.LT) {
return parseListOrMapLiteral(keyword);
} else if (type == TokenType.OPEN_SQUARE_BRACKET ||
type == TokenType.INDEX) {
return parseListLiteral(keyword, null);
} else if (type == TokenType.OPEN_CURLY_BRACKET) {
return parseMapLiteral(keyword, null);
}
return parseInstanceCreationExpression(keyword);
}
/// Parse a field initializer within a constructor. The flag [hasThis] should
/// be true if the current token is `this`. Return the field initializer that
/// was parsed.
///
/// fieldInitializer:
/// ('this' '.')? identifier '=' conditionalExpression cascadeSection*
ConstructorFieldInitializer parseConstructorFieldInitializer(bool hasThis) {
Token keywordToken = null;
Token period = null;
if (hasThis) {
keywordToken = getAndAdvance();
period = _expect(TokenType.PERIOD);
}
SimpleIdentifier fieldName = parseSimpleIdentifier();
Token equals = null;
TokenType type = _currentToken.type;
if (type == TokenType.EQ) {
equals = getAndAdvance();
} else {
_reportErrorForCurrentToken(
ParserErrorCode.MISSING_ASSIGNMENT_IN_INITIALIZER);
Keyword keyword = _currentToken.keyword;
if (keyword != Keyword.THIS &&
keyword != Keyword.SUPER &&
type != TokenType.OPEN_CURLY_BRACKET &&
type != TokenType.FUNCTION) {
equals = _createSyntheticToken(TokenType.EQ);
} else {
return astFactory.constructorFieldInitializer(
keywordToken,
period,
fieldName,
_createSyntheticToken(TokenType.EQ),
createSyntheticIdentifier());
}
}
bool wasInInitializer = _inInitializer;
_inInitializer = true;
try {
Expression expression = parseConditionalExpression();
if (_matches(TokenType.PERIOD_PERIOD)) {
List<Expression> cascadeSections = <Expression>[];
do {
Expression section = parseCascadeSection();
if (section != null) {
cascadeSections.add(section);
}
} while (_matches(TokenType.PERIOD_PERIOD));
expression = astFactory.cascadeExpression(expression, cascadeSections);
}
return astFactory.constructorFieldInitializer(
keywordToken, period, fieldName, equals, expression);
} finally {
_inInitializer = wasInInitializer;
}
}
/// Parse the name of a constructor. Return the constructor name that was
/// parsed.
///
/// constructorName:
/// type ('.' identifier)?
ConstructorName parseConstructorName() {
TypeName type = parseTypeName(false);
Token period = null;
SimpleIdentifier name = null;
if (_matches(TokenType.PERIOD)) {
period = getAndAdvance();
name = parseSimpleIdentifier();
}
return astFactory.constructorName(type, period, name);
}
/// Parse a continue statement. Return the continue statement that was parsed.
///
/// This method assumes that the current token matches `Keyword.CONTINUE`.
///
/// continueStatement ::=
/// 'continue' identifier? ';'
Statement parseContinueStatement() {
Token continueKeyword = getAndAdvance();
if (!_inLoop && !_inSwitch) {
_reportErrorForToken(
ParserErrorCode.CONTINUE_OUTSIDE_OF_LOOP, continueKeyword);
}
SimpleIdentifier label = null;
if (_matchesIdentifier()) {
label = _parseSimpleIdentifierUnchecked();
}
if (_inSwitch && !_inLoop && label == null) {
_reportErrorForToken(
ParserErrorCode.CONTINUE_WITHOUT_LABEL_IN_CASE, continueKeyword);
}
Token semicolon = _expect(TokenType.SEMICOLON);
return astFactory.continueStatement(continueKeyword, label, semicolon);
}
/// Parse a directive. The [commentAndMetadata] is the metadata to be
/// associated with the directive. Return the directive that was parsed.
///
/// directive ::=
/// exportDirective
/// | libraryDirective
/// | importDirective
/// | partDirective
Directive parseDirective(CommentAndMetadata commentAndMetadata) {
if (_matchesKeyword(Keyword.IMPORT)) {
return parseImportDirective(commentAndMetadata);
} else if (_matchesKeyword(Keyword.EXPORT)) {
return parseExportDirective(commentAndMetadata);
} else if (_matchesKeyword(Keyword.LIBRARY)) {
return parseLibraryDirective(commentAndMetadata);
} else if (_matchesKeyword(Keyword.PART)) {
return parsePartOrPartOfDirective(commentAndMetadata);
} else {
// Internal error: this method should not have been invoked if the current
// token was something other than one of the above.
throw new StateError(
"parseDirective invoked in an invalid state; currentToken = $_currentToken");
}
}
/// Parse the script tag and directives in a compilation unit, starting with
/// the given [token], until the first non-directive is encountered. The
/// remainder of the compilation unit will not be parsed. Specifically, if
/// there are directives later in the file, they will not be parsed. Return
/// the compilation unit that was parsed.
CompilationUnit parseDirectives(Token token) {
_currentToken = token;
return parseDirectives2();
}
/// Parse the script tag and directives in a compilation unit until the first
/// non-directive is encountered. Return the compilation unit that was parsed.
///
/// compilationUnit ::=
/// scriptTag? directive*
CompilationUnit parseDirectives2() {
Token firstToken = _currentToken;
ScriptTag scriptTag = null;
if (_matches(TokenType.SCRIPT_TAG)) {
scriptTag = astFactory.scriptTag(getAndAdvance());
}
List<Directive> directives = <Directive>[];
while (!_matches(TokenType.EOF)) {
CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
Keyword keyword = _currentToken.keyword;
TokenType type = _peek().type;
if ((keyword == Keyword.IMPORT ||
keyword == Keyword.EXPORT ||
keyword == Keyword.LIBRARY ||
keyword == Keyword.PART) &&
type != TokenType.PERIOD &&
type != TokenType.LT &&
type != TokenType.OPEN_PAREN) {
directives.add(parseDirective(commentAndMetadata));
} else if (_matches(TokenType.SEMICOLON)) {
_advance();
} else {
while (!_matches(TokenType.EOF)) {
_advance();
}
return astFactory.compilationUnit(
firstToken, scriptTag, directives, null, _currentToken);
}
}
return astFactory.compilationUnit(
firstToken, scriptTag, directives, null, _currentToken);
}
/// Parse a documentation comment based on the given list of documentation
/// comment tokens. Return the documentation comment that was parsed, or
/// `null` if there was no comment.
///
/// documentationComment ::=
/// multiLineComment?
/// | singleLineComment*
Comment parseDocumentationComment(List<DocumentationCommentToken> tokens) {
if (tokens == null) {
return null;
}
List<CommentReference> references = parseCommentReferences(tokens);
return astFactory.documentationComment(tokens, references);
}
/// Parse a documentation comment. Return the documentation comment that was
/// parsed, or `null` if there was no comment.
///
/// documentationComment ::=
/// multiLineComment?
/// | singleLineComment*
List<DocumentationCommentToken> parseDocumentationCommentTokens() {
List<DocumentationCommentToken> tokens = <DocumentationCommentToken>[];
CommentToken commentToken = _currentToken.precedingComments;
while (commentToken != null) {
if (commentToken is DocumentationCommentToken) {
if (tokens.isNotEmpty) {
if (commentToken.type == TokenType.SINGLE_LINE_COMMENT) {
if (tokens[0].type != TokenType.SINGLE_LINE_COMMENT) {
tokens.clear();
}
} else {
tokens.clear();
}
}
tokens.add(commentToken);
}
commentToken = commentToken.next;
}
return tokens.isEmpty ? null : tokens;
}
/// Parse a do statement. Return the do statement that was parsed.
///
/// This method assumes that the current token matches `Keyword.DO`.
///
/// doStatement ::=
/// 'do' statement 'while' '(' expression ')' ';'
Statement parseDoStatement() {
bool wasInLoop = _inLoop;
_inLoop = true;
try {
Token doKeyword = getAndAdvance();
Statement body = parseStatement2();
Token whileKeyword = _expectKeyword(Keyword.WHILE);
Token leftParenthesis = _expect(TokenType.OPEN_PAREN);
Expression condition = parseExpression2();
Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
Token semicolon = _expect(TokenType.SEMICOLON);
return astFactory.doStatement(doKeyword, body, whileKeyword,
leftParenthesis, condition, rightParenthesis, semicolon);
} finally {
_inLoop = wasInLoop;
}
}
/// Parse a dotted name. Return the dotted name that was parsed.
///
/// dottedName ::=
/// identifier ('.' identifier)*
DottedName parseDottedName() {
List<SimpleIdentifier> components = <SimpleIdentifier>[
parseSimpleIdentifier()
];
while (_optional(TokenType.PERIOD)) {
components.add(parseSimpleIdentifier());
}
return astFactory.dottedName(components);
}
/// Parse an empty statement. Return the empty statement that was parsed.
///
/// This method assumes that the current token matches `TokenType.SEMICOLON`.
///
/// emptyStatement ::=
/// ';'
Statement parseEmptyStatement() => astFactory.emptyStatement(getAndAdvance());
/// Parse an enum declaration. The [commentAndMetadata] is the metadata to be
/// associated with the member. Return the enum declaration that was parsed.
///
/// This method assumes that the current token matches `Keyword.ENUM`.
///
/// enumType ::=
/// metadata 'enum' id '{' id (',' id)* (',')? '}'
EnumDeclaration parseEnumDeclaration(CommentAndMetadata commentAndMetadata) {
Token keyword = getAndAdvance();
SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
Token leftBracket = null;
List<EnumConstantDeclaration> constants = <EnumConstantDeclaration>[];
Token rightBracket = null;
if (_matches(TokenType.OPEN_CURLY_BRACKET)) {
leftBracket = getAndAdvance();
if (_matchesIdentifier() || _matches(TokenType.AT)) {
constants.add(_parseEnumConstantDeclaration());
} else if (_matches(TokenType.COMMA) &&
_tokenMatchesIdentifier(_peek())) {
constants.add(_parseEnumConstantDeclaration());
_reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
} else {
constants.add(_parseEnumConstantDeclaration());
_reportErrorForCurrentToken(ParserErrorCode.EMPTY_ENUM_BODY);
}
while (_optional(TokenType.COMMA)) {
if (_matches(TokenType.CLOSE_CURLY_BRACKET)) {
break;
}
constants.add(_parseEnumConstantDeclaration());
}
rightBracket = _expect(TokenType.CLOSE_CURLY_BRACKET);
} else {
leftBracket = _createSyntheticToken(TokenType.OPEN_CURLY_BRACKET);
rightBracket = _createSyntheticToken(TokenType.CLOSE_CURLY_BRACKET);
_reportErrorForCurrentToken(ParserErrorCode.MISSING_ENUM_BODY);
}
return astFactory.enumDeclaration(
commentAndMetadata.comment,
commentAndMetadata.metadata,
keyword,
name,
leftBracket,
constants,
rightBracket);
}
/// Parse an equality expression. Return the equality expression that was
/// parsed.
///
/// equalityExpression ::=
/// relationalExpression (equalityOperator relationalExpression)?
/// | 'super' equalityOperator relationalExpression
Expression parseEqualityExpression() {
Expression expression;
if (_currentToken.keyword == Keyword.SUPER &&
_currentToken.next.type.isEqualityOperator) {
expression = astFactory.superExpression(getAndAdvance());
} else {
expression = parseRelationalExpression();
}
bool leftEqualityExpression = false;
while (_currentToken.type.isEqualityOperator) {
if (leftEqualityExpression) {
_reportErrorForNode(
ParserErrorCode.EQUALITY_CANNOT_BE_EQUALITY_OPERAND, expression);
}
expression = astFactory.binaryExpression(
expression, getAndAdvance(), parseRelationalExpression());
leftEqualityExpression = true;
}
return expression;
}
/// Parse an export directive. The [commentAndMetadata] is the metadata to be
/// associated with the directive. Return the export directive that was
/// parsed.
///
/// This method assumes that the current token matches `Keyword.EXPORT`.
///
/// exportDirective ::=
/// metadata 'export' stringLiteral configuration* combinator*';'
ExportDirective parseExportDirective(CommentAndMetadata commentAndMetadata) {
Token exportKeyword = getAndAdvance();
StringLiteral libraryUri = _parseUri();
List<Configuration> configurations = _parseConfigurations();
List<Combinator> combinators = parseCombinators();
Token semicolon = _expect(TokenType.SEMICOLON);
return astFactory.exportDirective(
commentAndMetadata.comment,
commentAndMetadata.metadata,
exportKeyword,
libraryUri,
configurations,
combinators,
semicolon);
}
/// Parse an expression, starting with the given [token]. Return the
/// expression that was parsed, or `null` if the tokens do not represent a
/// recognizable expression.
Expression parseExpression(Token token) {
_currentToken = token;
return parseExpression2();
}
/// Parse an expression that might contain a cascade. Return the expression
/// that was parsed.
///
/// expression ::=
/// assignableExpression assignmentOperator expression
/// | conditionalExpression cascadeSection*
/// | throwExpression
Expression parseExpression2() {
if (_treeDepth > _MAX_TREE_DEPTH) {
throw new _TooDeepTreeError();
}
_treeDepth++;
try {
Keyword keyword = _currentToken.keyword;
if (keyword == Keyword.THROW) {
return parseThrowExpression();
} else if (keyword == Keyword.RETHROW) {
// TODO(brianwilkerson) Rethrow is a statement again.
return parseRethrowExpression();
}
//
// assignableExpression is a subset of conditionalExpression, so we can
// parse a conditional expression and then determine whether it is followed
// by an assignmentOperator, checking for conformance to the restricted
// grammar after making that determination.
//
Expression expression = parseConditionalExpression();
TokenType type = _currentToken.type;
if (type == TokenType.PERIOD_PERIOD) {
List<Expression> cascadeSections = <Expression>[];
do {
Expression section = parseCascadeSection();
if (section != null) {
cascadeSections.add(section);
}
} while (_currentToken.type == TokenType.PERIOD_PERIOD);
return astFactory.cascadeExpression(expression, cascadeSections);
} else if (type.isAssignmentOperator) {
Token operator = getAndAdvance();
_ensureAssignable(expression);
return astFactory.assignmentExpression(
expression, operator, parseExpression2());
}
return expression;
} finally {
_treeDepth--;
}
}
/// Parse a list of expressions. Return the expression that was parsed.
///
/// expressionList ::=
/// expression (',' expression)*
List<Expression> parseExpressionList() {
List<Expression> expressions = <Expression>[parseExpression2()];
while (_optional(TokenType.COMMA)) {
expressions.add(parseExpression2());
}
return expressions;
}
/// Parse an expression that does not contain any cascades. Return the
/// expression that was parsed.
///
/// expressionWithoutCascade ::=
/// assignableExpression assignmentOperator expressionWithoutCascade
/// | conditionalExpression
/// | throwExpressionWithoutCascade
Expression parseExpressionWithoutCascade() {
if (_matchesKeyword(Keyword.THROW)) {
return parseThrowExpressionWithoutCascade();
} else if (_matchesKeyword(Keyword.RETHROW)) {
return parseRethrowExpression();
}
//
// assignableExpression is a subset of conditionalExpression, so we can
// parse a conditional expression and then determine whether it is followed
// by an assignmentOperator, checking for conformance to the restricted
// grammar after making that determination.
//
Expression expression = parseConditionalExpression();
if (_currentToken.type.isAssignmentOperator) {
Token operator = getAndAdvance();
_ensureAssignable(expression);
expression = astFactory.assignmentExpression(
expression, operator, parseExpressionWithoutCascade());
}
return expression;
}
/// Parse a class extends clause. Return the class extends clause that was
/// parsed.
///
/// This method assumes that the current token matches `Keyword.EXTENDS`.
///
/// classExtendsClause ::=
/// 'extends' type
ExtendsClause parseExtendsClause() {
Token keyword = getAndAdvance();
TypeName superclass = parseTypeName(false);
return astFactory.extendsClause(keyword, superclass);
}
/// Parse the 'final', 'const', 'var' or type preceding a variable
/// declaration. The [optional] is `true` if the keyword and type are
/// optional. Return the 'final', 'const', 'var' or type that was parsed.
///
/// finalConstVarOrType ::=
/// 'final' type?
/// | 'const' type?
/// | 'var'
/// | type
FinalConstVarOrType parseFinalConstVarOrType(bool optional,
{bool inFunctionType: false}) {
Token keywordToken = null;
TypeAnnotation type = null;
Keyword keyword = _currentToken.keyword;
if (keyword == Keyword.FINAL || keyword == Keyword.CONST) {
keywordToken = getAndAdvance();
if (_isTypedIdentifier(_currentToken)) {
type = parseTypeAnnotation(false);
}
} else if (keyword == Keyword.VAR) {
keywordToken = getAndAdvance();
} else if (_isTypedIdentifier(_currentToken)) {
type = parseTypeAnnotation(false);
} else if (inFunctionType && _matchesIdentifier()) {
type = parseTypeAnnotation(false);
} else if (!optional) {
// If there is a valid type immediately following an unexpected token,
// then report and skip the unexpected token.
Token next = _peek();
Keyword nextKeyword = next.keyword;
if (nextKeyword == Keyword.FINAL ||
nextKeyword == Keyword.CONST ||
nextKeyword == Keyword.VAR ||
_isTypedIdentifier(next) ||
inFunctionType && _tokenMatchesIdentifier(next)) {
_reportErrorForCurrentToken(
ParserErrorCode.UNEXPECTED_TOKEN, [_currentToken.lexeme]);
_advance();
return parseFinalConstVarOrType(optional,
inFunctionType: inFunctionType);
}
_reportErrorForCurrentToken(
ParserErrorCode.MISSING_CONST_FINAL_VAR_OR_TYPE);
} else {
// Support parameters such as `(/*=K*/ key, /*=V*/ value)`
// This is not supported if the type is required.
type = null;
}
return new FinalConstVarOrType(keywordToken, type);
}
/// Parse a formal parameter. At most one of `isOptional` and `isNamed` can be
/// `true`. The [kind] is the kind of parameter being expected based on the
/// presence or absence of group delimiters. Return the formal parameter that
/// was parsed.
///
/// defaultFormalParameter ::=
/// normalFormalParameter ('=' expression)?
///
/// defaultNamedParameter ::=
/// normalFormalParameter ('=' expression)?
/// normalFormalParameter (':' expression)?
FormalParameter parseFormalParameter(ParameterKind kind,
{bool inFunctionType: false}) {
NormalFormalParameter parameter =
parseNormalFormalParameter(inFunctionType: inFunctionType);
TokenType type = _currentToken.type;
if (type == TokenType.EQ) {
if (inFunctionType) {
_reportErrorForCurrentToken(
ParserErrorCode.DEFAULT_VALUE_IN_FUNCTION_TYPE);
}
Token separator = getAndAdvance();
Expression defaultValue = parseExpression2();
if (kind == ParameterKind.REQUIRED) {
_reportErrorForNode(
ParserErrorCode.POSITIONAL_PARAMETER_OUTSIDE_GROUP, parameter);
kind = ParameterKind.POSITIONAL;
} else if (kind == ParameterKind.NAMED &&
inFunctionType &&
parameter.identifier == null) {
_reportErrorForCurrentToken(
ParserErrorCode.MISSING_NAME_FOR_NAMED_PARAMETER);
parameter.identifier = createSyntheticIdentifier(isDeclaration: true);
}
return astFactory.defaultFormalParameter(
parameter, kind, separator, defaultValue);
} else if (type == TokenType.COLON) {
if (inFunctionType) {
_reportErrorForCurrentToken(
ParserErrorCode.DEFAULT_VALUE_IN_FUNCTION_TYPE);
}
Token separator = getAndAdvance();
Expression defaultValue = parseExpression2();
if (kind == ParameterKind.REQUIRED) {
_reportErrorForNode(
ParserErrorCode.NAMED_PARAMETER_OUTSIDE_GROUP, parameter);
kind = ParameterKind.NAMED;
} else if (kind == ParameterKind.POSITIONAL) {
_reportErrorForToken(
ParserErrorCode.WRONG_SEPARATOR_FOR_POSITIONAL_PARAMETER,
separator);
} else if (kind == ParameterKind.NAMED &&
inFunctionType &&
parameter.identifier == null) {
_reportErrorForCurrentToken(
ParserErrorCode.MISSING_NAME_FOR_NAMED_PARAMETER);
parameter.identifier = createSyntheticIdentifier(isDeclaration: true);
}
return astFactory.defaultFormalParameter(
parameter, kind, separator, defaultValue);
} else if (kind != ParameterKind.REQUIRED) {
if (kind == ParameterKind.NAMED &&
inFunctionType &&
parameter.identifier == null) {
_reportErrorForCurrentToken(
ParserErrorCode.MISSING_NAME_FOR_NAMED_PARAMETER);
parameter.identifier = createSyntheticIdentifier(isDeclaration: true);
}
return astFactory.defaultFormalParameter(parameter, kind, null, null);
}
return parameter;
}
/// Parse a list of formal parameters. Return the formal parameters that were
/// parsed.
///
/// formalParameterList ::=
/// '(' ')'
/// | '(' normalFormalParameters (',' optionalFormalParameters)? ')'
/// | '(' optionalFormalParameters ')'
///
/// normalFormalParameters ::=
/// normalFormalParameter (',' normalFormalParameter)*
///
/// optionalFormalParameters ::=
/// optionalPositionalFormalParameters
/// | namedFormalParameters
///
/// optionalPositionalFormalParameters ::=
/// '[' defaultFormalParameter (',' defaultFormalParameter)* ']'
///
/// namedFormalParameters ::=
/// '{' defaultNamedParameter (',' defaultNamedParameter)* '}'
FormalParameterList parseFormalParameterList({bool inFunctionType: false}) {
if (_matches(TokenType.OPEN_PAREN)) {
return _parseFormalParameterListUnchecked(inFunctionType: inFunctionType);
}
// TODO(brianwilkerson) Improve the error message.
_reportErrorForCurrentToken(
ParserErrorCode.EXPECTED_TOKEN, [TokenType.OPEN_PAREN.lexeme]);
// Recovery: Check for an unmatched closing paren and parse parameters until
// it is reached.
return _parseFormalParameterListAfterParen(
_createSyntheticToken(TokenType.OPEN_PAREN));
}
/// Parse a for statement. Return the for statement that was parsed.
///
/// forStatement ::=
/// 'for' '(' forLoopParts ')' statement
///
/// forLoopParts ::=
/// forInitializerStatement expression? ';' expressionList?
/// | declaredIdentifier 'in' expression
/// | identifier 'in' expression
///
/// forInitializerStatement ::=
/// localVariableDeclaration ';'
/// | expression? ';'
Statement parseForStatement() {
bool wasInLoop = _inLoop;
_inLoop = true;
try {
Token awaitKeyword = null;
if (_matchesKeyword(Keyword.AWAIT)) {
awaitKeyword = getAndAdvance();
}
Token forKeyword = _expectKeyword(Keyword.FOR);
Token leftParenthesis = _expect(TokenType.OPEN_PAREN);
VariableDeclarationList variableList = null;
Expression initialization = null;
if (!_matches(TokenType.SEMICOLON)) {
CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
if (_matchesIdentifier() &&
(_tokenMatchesKeyword(_peek(), Keyword.IN) ||
_tokenMatches(_peek(), TokenType.COLON))) {
SimpleIdentifier variableName = _parseSimpleIdentifierUnchecked();
variableList = astFactory.variableDeclarationList(
commentAndMetadata.comment,
commentAndMetadata.metadata,
null,
null, <VariableDeclaration>[
astFactory.variableDeclaration(variableName, null, null)
]);
} else if (isInitializedVariableDeclaration()) {
variableList =
parseVariableDeclarationListAfterMetadata(commentAndMetadata);
} else {
initialization = parseExpression2();
}
TokenType type = _currentToken.type;
if (_matchesKeyword(Keyword.IN) || type == TokenType.COLON) {
if (type == TokenType.COLON) {
_reportErrorForCurrentToken(ParserErrorCode.COLON_IN_PLACE_OF_IN);
}
DeclaredIdentifier loopVariable = null;
SimpleIdentifier identifier = null;
if (variableList == null) {
// We found: <expression> 'in'
_reportErrorForCurrentToken(
ParserErrorCode.MISSING_VARIABLE_IN_FOR_EACH);
} else {
NodeList<VariableDeclaration> variables = variableList.variables;
if (variables.length > 1) {
_reportErrorForCurrentToken(
ParserErrorCode.MULTIPLE_VARIABLES_IN_FOR_EACH,
[variables.length.toString()]);
}
VariableDeclaration variable = variables[0];
if (variable.initializer != null) {
_reportErrorForCurrentToken(
ParserErrorCode.INITIALIZED_VARIABLE_IN_FOR_EACH);
}
Token keyword = variableList.keyword;
TypeAnnotation type = variableList.type;
if (keyword != null || type != null) {
loopVariable = astFactory.declaredIdentifier(
commentAndMetadata.comment,
commentAndMetadata.metadata,
keyword,
type,
astFactory.simpleIdentifier(variable.name.token,
isDeclaration: true));
} else {
if (commentAndMetadata.hasMetadata) {
// TODO(jwren) metadata isn't allowed before the identifier in
// "identifier in expression", add warning if commentAndMetadata
// has content
}
identifier = variable.name;
}
}
Token inKeyword = getAndAdvance();
Expression iterator = parseExpression2();
Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
Statement body = parseStatement2();
ForLoopParts forLoopParts;
if (loopVariable == null) {
forLoopParts = astFactory.forEachPartsWithIdentifier(
identifier: identifier,
inKeyword: inKeyword,
iterable: iterator);
} else {
forLoopParts = astFactory.forEachPartsWithDeclaration(
loopVariable: loopVariable,
inKeyword: inKeyword,
iterable: iterator);
}
return astFactory.forStatement2(
forKeyword: forKeyword,
leftParenthesis: leftParenthesis,
forLoopParts: forLoopParts,
rightParenthesis: rightParenthesis,
body: body);
}
}
if (awaitKeyword != null) {
_reportErrorForToken(
ParserErrorCode.INVALID_AWAIT_IN_FOR, awaitKeyword);
}
Token leftSeparator = _expect(TokenType.SEMICOLON);
Expression condition = null;
if (!_matches(TokenType.SEMICOLON)) {
condition = parseExpression2();
}
Token rightSeparator = _expect(TokenType.SEMICOLON);
List<Expression> updaters = null;
if (!_matches(TokenType.CLOSE_PAREN)) {
updaters = parseExpressionList();
}
ForLoopParts forLoopParts;
if (variableList != null) {
forLoopParts = astFactory.forPartsWithDeclarations(
variables: variableList,
leftSeparator: leftSeparator,
condition: condition,
rightSeparator: rightSeparator,
updaters: updaters);
} else {
forLoopParts = astFactory.forPartsWithExpression(
initialization: initialization,
leftSeparator: leftSeparator,
condition: condition,
rightSeparator: rightSeparator,
updaters: updaters);
}
Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
Statement body = parseStatement2();
return astFactory.forStatement2(
forKeyword: forKeyword,
leftParenthesis: leftParenthesis,
forLoopParts: forLoopParts,
rightParenthesis: rightParenthesis,
body: body);
} finally {
_inLoop = wasInLoop;
}
}
/// Parse a function body. The [mayBeEmpty] is `true` if the function body is
/// allowed to be empty. The [emptyErrorCode] is the error code to report if
/// function body expected, but not found. The [inExpression] is `true` if the
/// function body is being parsed as part of an expression and therefore does
/// not have a terminating semicolon. Return the function body that was
/// parsed.
///
/// functionBody ::=
/// '=>' expression ';'
/// | block
///
/// functionExpressionBody ::=
/// '=>' expression
/// | block
FunctionBody parseFunctionBody(
bool mayBeEmpty, ParserErrorCode emptyErrorCode, bool inExpression) {
bool wasInAsync = _inAsync;
bool wasInGenerator = _inGenerator;
bool wasInLoop = _inLoop;
bool wasInSwitch = _inSwitch;
_inAsync = false;
_inGenerator = false;
_inLoop = false;
_inSwitch = false;
try {
TokenType type = _currentToken.type;
if (type == TokenType.SEMICOLON) {
if (!mayBeEmpty) {
_reportErrorForCurrentToken(emptyErrorCode);
}
return astFactory.emptyFunctionBody(getAndAdvance());
}
Token keyword = null;
Token star = null;
bool foundAsync = false;
bool foundSync = false;
if (type.isKeyword) {
String lexeme = _currentToken.lexeme;
if (lexeme == ASYNC) {
foundAsync = true;
keyword = getAndAdvance();
if (_matches(TokenType.STAR)) {
star = getAndAdvance();
_inGenerator = true;
}
type = _currentToken.type;
_inAsync = true;
} else if (lexeme == SYNC) {
foundSync = true;
keyword = getAndAdvance();
if (_matches(TokenType.STAR)) {
star = getAndAdvance();
_inGenerator = true;
}
type = _currentToken.type;
}
}
if (type == TokenType.FUNCTION) {
if (keyword != null) {
if (!foundAsync) {
_reportErrorForToken(ParserErrorCode.INVALID_SYNC, keyword);
keyword = null;
} else if (star != null) {
_reportErrorForToken(
ParserErrorCode.INVALID_STAR_AFTER_ASYNC, star);
}
}
Token functionDefinition = getAndAdvance();
if (_matchesKeyword(Keyword.RETURN)) {
_reportErrorForToken(ParserErrorCode.UNEXPECTED_TOKEN, _currentToken,
[_currentToken.lexeme]);
_advance();
}
Expression expression = parseExpression2();
Token semicolon = null;
if (!inExpression) {
semicolon = _expect(TokenType.SEMICOLON);
}
if (!_parseFunctionBodies) {
return astFactory
.emptyFunctionBody(_createSyntheticToken(TokenType.SEMICOLON));
}
return astFactory.expressionFunctionBody(
keyword, functionDefinition, expression, semicolon);
} else if (type == TokenType.OPEN_CURLY_BRACKET) {
if (keyword != null) {
if (foundSync && star == null) {
_reportErrorForToken(
ParserErrorCode.MISSING_STAR_AFTER_SYNC, keyword);
}
}
if (!_parseFunctionBodies) {
_skipBlock();
return astFactory
.emptyFunctionBody(_createSyntheticToken(TokenType.SEMICOLON));
}
return astFactory.blockFunctionBody(keyword, star, parseBlock());
} else if (_matchesKeyword(Keyword.NATIVE)) {
Token nativeToken = getAndAdvance();
StringLiteral stringLiteral = null;
if (_matches(TokenType.STRING)) {
stringLiteral = _parseStringLiteralUnchecked();
}
return astFactory.nativeFunctionBody(
nativeToken, stringLiteral, _expect(TokenType.SEMICOLON));
} else {
// Invalid function body
_reportErrorForCurrentToken(emptyErrorCode);
return astFactory
.emptyFunctionBody(_createSyntheticToken(TokenType.SEMICOLON));
}
} finally {
_inAsync = wasInAsync;
_inGenerator = wasInGenerator;
_inLoop = wasInLoop;
_inSwitch = wasInSwitch;
}
}
/// Parse a function declaration. The [commentAndMetadata] is the
/// documentation comment and metadata to be associated with the declaration.
/// The [externalKeyword] is the 'external' keyword, or `null` if the
/// function is not external. The [returnType] is the return type, or `null`
/// if there is no return type. The [isStatement] is `true` if the function
/// declaration is being parsed as a statement. Return the function
/// declaration that was parsed.
///
/// functionDeclaration ::=
/// functionSignature functionBody
/// | returnType? getOrSet identifier formalParameterList functionBody
FunctionDeclaration parseFunctionDeclaration(
CommentAndMetadata commentAndMetadata,
Token externalKeyword,
TypeAnnotation returnType) {
Token keywordToken = null;
bool isGetter = false;
Keyword keyword = _currentToken.keyword;
SimpleIdentifier name = null;
if (keyword == Keyword.GET) {
keywordToken = getAndAdvance();
isGetter = true;
} else if (keyword == Keyword.SET) {
keywordToken = getAndAdvance();
}
if (keywordToken != null && _matches(TokenType.OPEN_PAREN)) {
name = astFactory.simpleIdentifier(keywordToken, isDeclaration: true);
keywordToken = null;
isGetter = false;
} else {
name = parseSimpleIdentifier(isDeclaration: true);
}
TypeParameterList typeParameters = _parseGenericMethodTypeParameters();
FormalParameterList parameters = null;
if (!isGetter) {
if (_matches(TokenType.OPEN_PAREN)) {
parameters = _parseFormalParameterListUnchecked();
_validateFormalParameterList(parameters);
} else {
_reportErrorForCurrentToken(
ParserErrorCode.MISSING_FUNCTION_PARAMETERS);
parameters = astFactory.formalParameterList(
_createSyntheticToken(TokenType.OPEN_PAREN),
null,
null,
null,
_createSyntheticToken(TokenType.CLOSE_PAREN));
}
} else if (_matches(TokenType.OPEN_PAREN)) {
_reportErrorForCurrentToken(ParserErrorCode.GETTER_WITH_PARAMETERS);
_parseFormalParameterListUnchecked();
}
FunctionBody body;
if (externalKeyword == null) {
body = parseFunctionBody(
false, ParserErrorCode.MISSING_FUNCTION_BODY, false);
} else {
body = astFactory.emptyFunctionBody(_expect(TokenType.SEMICOLON));
}
// if (!isStatement && matches(TokenType.SEMICOLON)) {
// // TODO(brianwilkerson) Improve this error message.
// reportError(ParserErrorCode.UNEXPECTED_TOKEN, currentToken.getLexeme());
// advance();
// }
return astFactory.functionDeclaration(
commentAndMetadata.comment,
commentAndMetadata.metadata,
externalKeyword,
returnType,
keywordToken,
name,
astFactory.functionExpression(typeParameters, parameters, body));
}
/// Parse a function declaration statement. Return the function declaration
/// statement that was parsed.
///
/// functionDeclarationStatement ::=
/// functionSignature functionBody
Statement parseFunctionDeclarationStatement() {
Modifiers modifiers = parseModifiers();
_validateModifiersForFunctionDeclarationStatement(modifiers);
return _parseFunctionDeclarationStatementAfterReturnType(
parseCommentAndMetadata(), _parseOptionalReturnType());
}
/// Parse a function expression. Return the function expression that was
/// parsed.
///
/// functionExpression ::=
/// typeParameters? formalParameterList functionExpressionBody
FunctionExpression parseFunctionExpression() {
TypeParameterList typeParameters = _parseGenericMethodTypeParameters();
FormalParameterList parameters = parseFormalParameterList();
_validateFormalParameterList(parameters);
FunctionBody body =
parseFunctionBody(false, ParserErrorCode.MISSING_FUNCTION_BODY, true);
return astFactory.functionExpression(typeParameters, parameters, body);
}
/// Parse the portion of a generic function type following the [returnType].
///
/// functionType ::=
/// returnType? 'Function' typeParameters? parameterTypeList
/// parameterTypeList ::=
/// '(' ')' |
/// | '(' normalParameterTypes ','? ')' |
/// | '(' normalParameterTypes ',' optionalParameterTypes ')' |
/// | '(' optionalParameterTypes ')'
/// normalParameterTypes ::=
/// normalParameterType (',' normalParameterType)*
/// normalParameterType ::=
/// type | typedIdentifier
/// optionalParameterTypes ::=
/// optionalPositionalParameterTypes | namedParameterTypes
/// optionalPositionalParameterTypes ::=
/// '[' normalParameterTypes ','? ']'
/// namedParameterTypes ::=
/// '{' typedIdentifier (',' typedIdentifier)* ','? '}'
/// typedIdentifier ::=
/// type identifier
GenericFunctionType parseGenericFunctionTypeAfterReturnType(
TypeAnnotation returnType) {
Token functionKeyword = null;
if (_matchesKeyword(Keyword.FUNCTION)) {
functionKeyword = getAndAdvance();
} else if (_matchesIdentifier()) {
_reportErrorForCurrentToken(ParserErrorCode.NAMED_FUNCTION_TYPE);
} else {
_reportErrorForCurrentToken(ParserErrorCode.MISSING_FUNCTION_KEYWORD);
}
TypeParameterList typeParameters = null;
if (_matches(TokenType.LT)) {
typeParameters = parseTypeParameterList();
}
FormalParameterList parameters =
parseFormalParameterList(inFunctionType: true);
return astFactory.genericFunctionType(
returnType, functionKeyword, typeParameters, parameters);
}
/// Parse a generic function type alias.
///
/// This method assumes that the current token is an identifier.
///
/// genericTypeAlias ::=
/// 'typedef' identifier typeParameterList? '=' functionType ';'
GenericTypeAlias parseGenericTypeAlias(
CommentAndMetadata commentAndMetadata, Token keyword) {
Identifier name = _parseSimpleIdentifierUnchecked(isDeclaration: true);
TypeParameterList typeParameters = null;
if (_matches(TokenType.LT)) {
typeParameters = parseTypeParameterList();
}
Token equals = _expect(TokenType.EQ);
TypeAnnotation functionType = parseTypeAnnotation(false);
Token semicolon = _expect(TokenType.SEMICOLON);
if (functionType is! GenericFunctionType) {
// TODO(brianwilkerson) Generate a better error.
_reportErrorForToken(
ParserErrorCode.INVALID_GENERIC_FUNCTION_TYPE, semicolon);
// TODO(brianwilkerson) Recover better than this.
return astFactory.genericTypeAlias(
commentAndMetadata.comment,
commentAndMetadata.metadata,
keyword,
name,
typeParameters,
equals,
null,
semicolon);
}
return astFactory.genericTypeAlias(
commentAndMetadata.comment,
commentAndMetadata.metadata,
keyword,
name,
typeParameters,
equals,
functionType,
semicolon);
}
/// Parse a getter. The [commentAndMetadata] is the documentation comment and
/// metadata to be associated with the declaration. The externalKeyword] is
/// the 'external' token. The staticKeyword] is the static keyword, or `null`
/// if the getter is not static. The [returnType] the return type that has
/// already been parsed, or `null` if there was no return type. Return the
/// getter that was parsed.
///
/// This method assumes that the current token matches `Keyword.GET`.
///
/// getter ::=
/// getterSignature functionBody?
///
/// getterSignature ::=
/// 'external'? 'static'? returnType? 'get' identifier
MethodDeclaration parseGetter(CommentAndMetadata commentAndMetadata,
Token externalKeyword, Token staticKeyword, TypeAnnotation returnType) {
Token propertyKeyword = getAndAdvance();
SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
if (_matches(TokenType.OPEN_PAREN) &&
_tokenMatches(_peek(), TokenType.CLOSE_PAREN)) {
_reportErrorForCurrentToken(ParserErrorCode.GETTER_WITH_PARAMETERS);
_advance();
_advance();
}
FunctionBody body = parseFunctionBody(
externalKeyword != null || staticKeyword == null,
ParserErrorCode.STATIC_GETTER_WITHOUT_BODY,
false);
if (externalKeyword != null && body is! EmptyFunctionBody) {
_reportErrorForCurrentToken(ParserErrorCode.EXTERNAL_GETTER_WITH_BODY);
}
return astFactory.methodDeclaration(
commentAndMetadata.comment,
commentAndMetadata.metadata,
externalKeyword,
staticKeyword,
returnType,
propertyKeyword,
null,
name,
null,
null,
body);
}
/// Parse a list of identifiers. Return the list of identifiers that were
/// parsed.
///
/// identifierList ::=
/// identifier (',' identifier)*
List<SimpleIdentifier> parseIdentifierList() {
List<SimpleIdentifier> identifiers = <SimpleIdentifier>[
parseSimpleIdentifier()
];
while (_optional(TokenType.COMMA)) {
identifiers.add(parseSimpleIdentifier());
}
return identifiers;
}
/// Parse an if-null expression. Return the if-null expression that was
/// parsed.
///
/// ifNullExpression ::= logicalOrExpression ('??' logicalOrExpression)*
Expression parseIfNullExpression() {
Expression expression = parseLogicalOrExpression();
while (_currentToken.type == TokenType.QUESTION_QUESTION) {
expression = astFactory.binaryExpression(
expression, getAndAdvance(), parseLogicalOrExpression());
}
return expression;
}
/// Parse an if statement. Return the if statement that was parsed.
///
/// This method assumes that the current token matches `Keyword.IF`.
///
/// ifStatement ::=
/// 'if' '(' expression ')' statement ('else' statement)?
Statement parseIfStatement() {
Token ifKeyword = getAndAdvance();
Token leftParenthesis = _expect(TokenType.OPEN_PAREN);
Expression condition = parseExpression2();
Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
Statement thenStatement = parseStatement2();
Token elseKeyword = null;
Statement elseStatement = null;
if (_matchesKeyword(Keyword.ELSE)) {
elseKeyword = getAndAdvance();
elseStatement = parseStatement2();
}
return astFactory.ifStatement(ifKeyword, leftParenthesis, condition,
rightParenthesis, thenStatement, elseKeyword, elseStatement);
}
/// Parse an implements clause. Return the implements clause that was parsed.
///
/// This method assumes that the current token matches `Keyword.IMPLEMENTS`.
///
/// implementsClause ::=
/// 'implements' type (',' type)*
ImplementsClause parseImplementsClause() {
Token keyword = getAndAdvance();
List<TypeName> interfaces = <TypeName>[];
do {
TypeName typeName = parseTypeName(false);
interfaces.add(typeName);
} while (_optional(TokenType.COMMA));
return astFactory.implementsClause(keyword, interfaces);
}
/// Parse an import directive. The [commentAndMetadata] is the metadata to be
/// associated with the directive. Return the import directive that was
/// parsed.
///
/// This method assumes that the current token matches `Keyword.IMPORT`.
///
/// importDirective ::=
/// metadata 'import' stringLiteral configuration* (deferred)? ('as' identifier)? combinator*';'
ImportDirective parseImportDirective(CommentAndMetadata commentAndMetadata) {
Token importKeyword = getAndAdvance();
StringLiteral libraryUri = _parseUri();
List<Configuration> configurations = _parseConfigurations();
Token deferredToken = null;
Token asToken = null;
SimpleIdentifier prefix = null;
if (_matchesKeyword(Keyword.DEFERRED)) {
deferredToken = getAndAdvance();
}
if (_matchesKeyword(Keyword.AS)) {
asToken = getAndAdvance();
prefix = parseSimpleIdentifier(isDeclaration: true);
} else if (deferredToken != null) {
_reportErrorForCurrentToken(
ParserErrorCode.MISSING_PREFIX_IN_DEFERRED_IMPORT);
} else if (!_matches(TokenType.SEMICOLON) &&
!_matchesKeyword(Keyword.SHOW) &&
!_matchesKeyword(Keyword.HIDE)) {
Token nextToken = _peek();
if (_tokenMatchesKeyword(nextToken, Keyword.AS) ||
_tokenMatchesKeyword(nextToken, Keyword.SHOW) ||
_tokenMatchesKeyword(nextToken, Keyword.HIDE)) {
_reportErrorForCurrentToken(
ParserErrorCode.UNEXPECTED_TOKEN, [_currentToken]);
_advance();
if (_matchesKeyword(Keyword.AS)) {
asToken = getAndAdvance();
prefix = parseSimpleIdentifier(isDeclaration: true);
}
}
}
List<Combinator> combinators = parseCombinators();
Token semicolon = _expect(TokenType.SEMICOLON);
return astFactory.importDirective(
commentAndMetadata.comment,
commentAndMetadata.metadata,
importKeyword,
libraryUri,
configurations,
deferredToken,
asToken,
prefix,
combinators,
semicolon);
}
/// Parse a list of initialized identifiers. The [commentAndMetadata] is the
/// documentation comment and metadata to be associated with the declaration.
/// The [staticKeyword] is the static keyword, or `null` if the getter is not
/// static. The [keyword] is the token representing the 'final', 'const' or
/// 'var' keyword, or `null` if there is no keyword. The [type] is the type
/// that has already been parsed, or `null` if 'var' was provided. Return the
/// getter that was parsed.
///
/// declaration ::=
/// ('static' | 'covariant')? ('var' | type) initializedIdentifierList ';'
/// | 'final' type? initializedIdentifierList ';'
///
/// initializedIdentifierList ::=
/// initializedIdentifier (',' initializedIdentifier)*
///
/// initializedIdentifier ::=
/// identifier ('=' expression)?
FieldDeclaration parseInitializedIdentifierList(
CommentAndMetadata commentAndMetadata,
Token staticKeyword,
Token covariantKeyword,
Token keyword,
TypeAnnotation type) {
VariableDeclarationList fieldList =
parseVariableDeclarationListAfterType(null, keyword, type);
return astFactory.fieldDeclaration2(
comment: commentAndMetadata.comment,
metadata: commentAndMetadata.metadata,
covariantKeyword: covariantKeyword,
staticKeyword: staticKeyword,
fieldList: fieldList,
semicolon: _expect(TokenType.SEMICOLON));
}
/// Parse an instance creation expression. The [keyword] is the 'new' or
/// 'const' keyword that introduces the expression. Return the instance
/// creation expression that was parsed.
///
/// instanceCreationExpression ::=
/// ('new' | 'const') type ('.' identifier)? argumentList
InstanceCreationExpression parseInstanceCreationExpression(Token keyword) {
ConstructorName constructorName = parseConstructorName();
ArgumentList argumentList = _parseArgumentListChecked();
return astFactory.instanceCreationExpression(
keyword, constructorName, argumentList);
}
/// Parse a label. Return the label that was parsed.
///
/// This method assumes that the current token matches an identifier and that
/// the following token matches `TokenType.COLON`.
///
/// label ::=
/// identifier ':'
Label parseLabel({bool isDeclaration: false}) {
SimpleIdentifier label =
_parseSimpleIdentifierUnchecked(isDeclaration: isDeclaration);
Token colon = getAndAdvance();
return astFactory.label(label, colon);
}
/// Parse a library directive. The [commentAndMetadata] is the metadata to be
/// associated with the directive. Return the library directive that was
/// parsed.
///
/// This method assumes that the current token matches `Keyword.LIBRARY`.
///
/// libraryDirective ::=
/// metadata 'library' identifier ';'
LibraryDirective parseLibraryDirective(
CommentAndMetadata commentAndMetadata) {
Token keyword = getAndAdvance();
LibraryIdentifier libraryName = _parseLibraryName(
ParserErrorCode.MISSING_NAME_IN_LIBRARY_DIRECTIVE, keyword);
Token semicolon = _expect(TokenType.SEMICOLON);
return astFactory.libraryDirective(commentAndMetadata.comment,
commentAndMetadata.metadata, keyword, libraryName, semicolon);
}
/// Parse a library identifier. Return the library identifier that was parsed.
///
/// libraryIdentifier ::=
/// identifier ('.' identifier)*
LibraryIdentifier parseLibraryIdentifier() {
List<SimpleIdentifier> components = <SimpleIdentifier>[];
components.add(parseSimpleIdentifier());
while (_optional(TokenType.PERIOD)) {
components.add(parseSimpleIdentifier());
}
return astFactory.libraryIdentifier(components);
}
/// Parse a list literal. The [modifier] is the 'const' modifier appearing
/// before the literal, or `null` if there is no modifier. The [typeArguments]
/// is the type arguments appearing before the literal, or `null` if there are
/// no type arguments. Return the list literal that was parsed.
///
/// This method assumes that the current token matches either
/// `TokenType.OPEN_SQUARE_BRACKET` or `TokenType.INDEX`.
///
/// listLiteral ::=
/// 'const'? typeArguments? '[' (expressionList ','?)? ']'
ListLiteral parseListLiteral(Token modifier, TypeArgumentList typeArguments) {
if (_matches(TokenType.INDEX)) {
_splitIndex();
return astFactory.listLiteral(
modifier, typeArguments, getAndAdvance(), null, getAndAdvance());
}
Token leftBracket = getAndAdvance();
if (_matches(TokenType.CLOSE_SQUARE_BRACKET)) {
return astFactory.listLiteral(
modifier, typeArguments, leftBracket, null, getAndAdvance());
}
bool wasInInitializer = _inInitializer;
_inInitializer = false;
try {
List<Expression> elements = <Expression>[parseExpression2()];
while (_optional(TokenType.COMMA)) {
if (_matches(TokenType.CLOSE_SQUARE_BRACKET)) {
return astFactory.listLiteral(
modifier, typeArguments, leftBracket, elements, getAndAdvance());
}
elements.add(parseExpression2());
}
Token rightBracket = _expect(TokenType.CLOSE_SQUARE_BRACKET);
return astFactory.listLiteral(
modifier, typeArguments, leftBracket, elements, rightBracket);
} finally {
_inInitializer = wasInInitializer;
}
}
/// Parse a list or map literal. The [modifier] is the 'const' modifier
/// appearing before the literal, or `null` if there is no modifier. Return
/// the list or map literal that was parsed.
///
/// listOrMapLiteral ::=
/// listLiteral
/// | mapLiteral
TypedLiteral parseListOrMapLiteral(Token modifier) {
TypeArgumentList typeArguments = _parseOptionalTypeArguments();
if (_matches(TokenType.OPEN_CURLY_BRACKET)) {
return parseMapLiteral(modifier, typeArguments);
} else if (_matches(TokenType.OPEN_SQUARE_BRACKET) ||
_matches(TokenType.INDEX)) {
return parseListLiteral(modifier, typeArguments);
}
_reportErrorForCurrentToken(ParserErrorCode.EXPECTED_LIST_OR_MAP_LITERAL);
return astFactory.listLiteral(
modifier,
typeArguments,
_createSyntheticToken(TokenType.OPEN_SQUARE_BRACKET),
null,
_createSyntheticToken(TokenType.CLOSE_SQUARE_BRACKET));
}
/// Parse a logical and expression. Return the logical and expression that was
/// parsed.
///
/// logicalAndExpression ::=
/// equalityExpression ('&&' equalityExpression)*
Expression parseLogicalAndExpression() {
Expression expression = parseEqualityExpression();
while (_currentToken.type == TokenType.AMPERSAND_AMPERSAND) {
expression = astFactory.binaryExpression(
expression, getAndAdvance(), parseEqualityExpression());
}
return expression;
}
/// Parse a logical or expression. Return the logical or expression that was
/// parsed.
///
/// logicalOrExpression ::=
/// logicalAndExpression ('||' logicalAndExpression)*
Expression parseLogicalOrExpression() {
Expression expression = parseLogicalAndExpression();
while (_currentToken.type == TokenType.BAR_BAR) {
expression = astFactory.binaryExpression(
expression, getAndAdvance(), parseLogicalAndExpression());
}
return expression;
}
/// Parse a map literal. The [modifier] is the 'const' modifier appearing
/// before the literal, or `null` if there is no modifier. The [typeArguments]
/// is the type arguments that were declared, or `null` if there are no type
/// arguments. Return the map literal that was parsed.
///
/// This method assumes that the current token matches
/// `TokenType.OPEN_CURLY_BRACKET`.
///
/// mapLiteral ::=
/// 'const'? typeArguments? '{' (mapLiteralEntry (',' mapLiteralEntry)* ','?)? '}'
MapLiteral // ignore: deprecated_member_use_from_same_package
parseMapLiteral(Token modifier, TypeArgumentList typeArguments) {
Token leftBracket = getAndAdvance();
if (_matches(TokenType.CLOSE_CURLY_BRACKET)) {
// ignore: deprecated_member_use_from_same_package
return astFactory.mapLiteral(
modifier, typeArguments, leftBracket, null, getAndAdvance());
}
bool wasInInitializer = _inInitializer;
_inInitializer = false;
try {
List<MapLiteralEntry> entries = <MapLiteralEntry>[parseMapLiteralEntry()];
while (_optional(TokenType.COMMA)) {
if (_matches(TokenType.CLOSE_CURLY_BRACKET)) {
// ignore: deprecated_member_use_from_same_package
return astFactory.mapLiteral(
modifier, typeArguments, leftBracket, entries, getAndAdvance());
}
entries.add(parseMapLiteralEntry());
}
Token rightBracket = _expect(TokenType.CLOSE_CURLY_BRACKET);
// ignore: deprecated_member_use_from_same_package
return astFactory.mapLiteral(
modifier, typeArguments, leftBracket, entries, rightBracket);
} finally {
_inInitializer = wasInInitializer;
}
}
/// Parse a map literal entry. Return the map literal entry that was parsed.
///
/// mapLiteralEntry ::=
/// expression ':' expression
MapLiteralEntry parseMapLiteralEntry() {
Expression key = parseExpression2();
Token separator = _expect(TokenType.COLON);
Expression value = parseExpression2();
return astFactory.mapLiteralEntry(key, separator, value);
}
/// Parse the modifiers preceding a declaration. This method allows the
/// modifiers to appear in any order but does generate errors for duplicated
/// modifiers. Checks for other problems, such as having the modifiers appear
/// in the wrong order or specifying both 'const' and 'final', are reported in
/// one of the methods whose name is prefixed with `validateModifiersFor`.
/// Return the modifiers that were parsed.
///
/// modifiers ::=
/// ('abstract' | 'const' | 'external' | 'factory' | 'final' | 'static' | 'var')*
Modifiers parseModifiers() {
Modifiers modifiers = new Modifiers();
bool progress = true;
while (progress) {
TokenType nextType = _peek().type;
if (nextType == TokenType.PERIOD ||
nextType == TokenType.LT ||
nextType == TokenType.OPEN_PAREN) {
return modifiers;
}
Keyword keyword = _currentToken.keyword;
if (keyword == Keyword.ABSTRACT) {
if (modifiers.abstractKeyword != null) {
_reportErrorForCurrentToken(
ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
_advance();
} else {
modifiers.abstractKeyword = getAndAdvance();
}
} else if (keyword == Keyword.CONST) {
if (modifiers.constKeyword != null) {
_reportErrorForCurrentToken(
ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
_advance();
} else {
modifiers.constKeyword = getAndAdvance();
}
} else if (keyword == Keyword.COVARIANT) {
if (modifiers.covariantKeyword != null) {
_reportErrorForCurrentToken(
ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
_advance();
} else {
modifiers.covariantKeyword = getAndAdvance();
}
} else if (keyword == Keyword.EXTERNAL) {
if (modifiers.externalKeyword != null) {
_reportErrorForCurrentToken(
ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
_advance();
} else {
modifiers.externalKeyword = getAndAdvance();
}
} else if (keyword == Keyword.FACTORY) {
if (modifiers.factoryKeyword != null) {
_reportErrorForCurrentToken(
ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
_advance();
} else {
modifiers.factoryKeyword = getAndAdvance();
}
} else if (keyword == Keyword.FINAL) {
if (modifiers.finalKeyword != null) {
_reportErrorForCurrentToken(
ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
_advance();
} else {
modifiers.finalKeyword = getAndAdvance();
}
} else if (keyword == Keyword.STATIC) {
if (modifiers.staticKeyword != null) {
_reportErrorForCurrentToken(
ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
_advance();
} else {
modifiers.staticKeyword = getAndAdvance();
}
} else if (keyword == Keyword.VAR) {
if (modifiers.varKeyword != null) {
_reportErrorForCurrentToken(
ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
_advance();
} else {
modifiers.varKeyword = getAndAdvance();
}
} else {
progress = false;
}
}
return modifiers;
}
/// Parse a multiplicative expression. Return the multiplicative expression
/// that was parsed.
///
/// multiplicativeExpression ::=
/// unaryExpression (multiplicativeOperator unaryExpression)*
/// | 'super' (multiplicativeOperator unaryExpression)+
Expression parseMultiplicativeExpression() {
Expression expression;
if (_currentToken.keyword == Keyword.SUPER &&
_currentToken.next.type.isMultiplicativeOperator) {
expression = astFactory.superExpression(getAndAdvance());
} else {
expression = parseUnaryExpression();
}
while (_currentToken.type.isMultiplicativeOperator) {
expression = astFactory.binaryExpression(
expression, getAndAdvance(), parseUnaryExpression());
}
return expression;
}
/// Parse a new expression. Return the new expression that was parsed.
///
/// This method assumes that the current token matches `Keyword.NEW`.
///
/// newExpression ::=
/// instanceCreationExpression
InstanceCreationExpression parseNewExpression() =>
parseInstanceCreationExpression(getAndAdvance());
/// Parse a non-labeled statement. Return the non-labeled statement that was
/// parsed.
///
/// nonLabeledStatement ::=
/// block
/// | assertStatement
/// | breakStatement
/// | continueStatement
/// | doStatement
/// | forStatement
/// | ifStatement
/// | returnStatement
/// | switchStatement
/// | tryStatement
/// | whileStatement
/// | variableDeclarationList ';'
/// | expressionStatement
/// | functionSignature functionBody
Statement parseNonLabeledStatement() {
// TODO(brianwilkerson) Pass the comment and metadata on where appropriate.
CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
TokenType type = _currentToken.type;
if (type == TokenType.OPEN_CURLY_BRACKET) {
if (_tokenMatches(_peek(), TokenType.STRING)) {
Token afterString = skipStringLiteral(_currentToken.next);
if (afterString != null && afterString.type == TokenType.COLON) {
return astFactory.expressionStatement(
parseExpression2(), _expect(TokenType.SEMICOLON));
}
}
return parseBlock();
} else if (type.isKeyword && !_currentToken.keyword.isBuiltInOrPseudo) {
Keyword keyword = _currentToken.keyword;
// TODO(jwren) compute some metrics to figure out a better order for this
// if-then sequence to optimize performance
if (keyword == Keyword.ASSERT) {
return parseAssertStatement();
} else if (keyword == Keyword.BREAK) {
return parseBreakStatement();
} else if (keyword == Keyword.CONTINUE) {
return parseContinueStatement();
} else if (keyword == Keyword.DO) {
return parseDoStatement();
} else if (keyword == Keyword.FOR) {
return parseForStatement();
} else if (keyword == Keyword.IF) {
return parseIfStatement();
} else if (keyword == Keyword.RETHROW) {
return astFactory.expressionStatement(
parseRethrowExpression(), _expect(TokenType.SEMICOLON));
} else if (keyword == Keyword.RETURN) {
return parseReturnStatement();
} else if (keyword == Keyword.SWITCH) {
return parseSwitchStatement();
} else if (keyword == Keyword.THROW) {
return astFactory.expressionStatement(
parseThrowExpression(), _expect(TokenType.SEMICOLON));
} else if (keyword == Keyword.TRY) {
return parseTryStatement();
} else if (keyword == Keyword.WHILE) {
return parseWhileStatement();
} else if (keyword == Keyword.VAR || keyword == Keyword.FINAL) {
return parseVariableDeclarationStatementAfterMetadata(
commentAndMetadata);
} else if (keyword == Keyword.VOID) {
TypeAnnotation returnType;
if (_atGenericFunctionTypeAfterReturnType(_peek())) {
returnType = parseTypeAnnotation(false);
} else {
returnType = astFactory.typeName(
astFactory.simpleIdentifier(getAndAdvance()), null);
}
Token next = _currentToken.next;
if (_matchesIdentifier() &&
next.matchesAny(const <TokenType>[
TokenType.OPEN_PAREN,
TokenType.OPEN_CURLY_BRACKET,
TokenType.FUNCTION,
TokenType.LT
])) {
return _parseFunctionDeclarationStatementAfterReturnType(
commentAndMetadata, returnType);
} else if (_matchesIdentifier() &&
next.matchesAny(const <TokenType>[
TokenType.EQ,
TokenType.COMMA,
TokenType.SEMICOLON
])) {
return _parseVariableDeclarationStatementAfterType(
commentAndMetadata, null, returnType);
} else {
//
// We have found an error of some kind. Try to recover.
//
if (_matches(TokenType.CLOSE_CURLY_BRACKET)) {
//
// We appear to have found an incomplete statement at the end of a
// block. Parse it as a variable declaration.
//
return _parseVariableDeclarationStatementAfterType(
commentAndMetadata, null, returnType);
}
_reportErrorForCurrentToken(ParserErrorCode.MISSING_STATEMENT);
// TODO(brianwilkerson) Recover from this error.
return astFactory
.emptyStatement(_createSyntheticToken(TokenType.SEMICOLON));
}
} else if (keyword == Keyword.CONST) {
Token next = _currentToken.next;
if (next.matchesAny(const <TokenType>[
TokenType.LT,
TokenType.OPEN_CURLY_BRACKET,
TokenType.OPEN_SQUARE_BRACKET,
TokenType.INDEX
])) {
return astFactory.expressionStatement(
parseExpression2(), _expect(TokenType.SEMICOLON));
} else if (_tokenMatches(next, TokenType.IDENTIFIER)) {
Token afterType = skipTypeName(next);
if (afterType != null) {
if (_tokenMatches(afterType, TokenType.OPEN_PAREN) ||
(_tokenMatches(afterType, TokenType.PERIOD) &&
_tokenMatches(afterType.next, TokenType.IDENTIFIER) &&
_tokenMatches(afterType.next.next, TokenType.OPEN_PAREN))) {
return astFactory.expressionStatement(
parseExpression2(), _expect(TokenType.SEMICOLON));
}
}
}
return parseVariableDeclarationStatementAfterMetadata(
commentAndMetadata);
} else if (keyword == Keyword.NEW ||
keyword == Keyword.TRUE ||
keyword == Keyword.FALSE ||
keyword == Keyword.NULL ||
keyword == Keyword.SUPER ||
keyword == Keyword.THIS) {
return astFactory.expressionStatement(
parseExpression2(), _expect(TokenType.SEMICOLON));
} else {
//
// We have found an error of some kind. Try to recover.
//
_reportErrorForCurrentToken(ParserErrorCode.MISSING_STATEMENT);
return astFactory
.emptyStatement(_createSyntheticToken(TokenType.SEMICOLON));
}
} else if (_atGenericFunctionTypeAfterReturnType(_currentToken)) {
TypeAnnotation returnType = parseTypeAnnotation(false);
Token next = _currentToken.next;
if (_matchesIdentifier() &&
next.matchesAny(const <TokenType>[
TokenType.OPEN_PAREN,
TokenType.OPEN_CURLY_BRACKET,
TokenType.FUNCTION,
TokenType.LT
])) {
return _parseFunctionDeclarationStatementAfterReturnType(
commentAndMetadata, returnType);
} else if (_matchesIdentifier() &&
next.matchesAny(const <TokenType>[
TokenType.EQ,
TokenType.COMMA,
TokenType.SEMICOLON
])) {
return _parseVariableDeclarationStatementAfterType(
commentAndMetadata, null, returnType);
} else {
//
// We have found an error of some kind. Try to recover.
//
if (_matches(TokenType.CLOSE_CURLY_BRACKET)) {
//
// We appear to have found an incomplete statement at the end of a
// block. Parse it as a variable declaration.
//
return _parseVariableDeclarationStatementAfterType(
commentAndMetadata, null, returnType);
}
_reportErrorForCurrentToken(ParserErrorCode.MISSING_STATEMENT);
// TODO(brianwilkerson) Recover from this error.
return astFactory
.emptyStatement(_createSyntheticToken(TokenType.SEMICOLON));
}
} else if (_inGenerator && _matchesKeyword(Keyword.YIELD)) {
return parseYieldStatement();
} else if (_inAsync && _matchesKeyword(Keyword.AWAIT)) {
if (_tokenMatchesKeyword(_peek(), Keyword.FOR)) {
return parseForStatement();
}
return astFactory.expressionStatement(
parseExpression2(), _expect(TokenType.SEMICOLON));
} else if (_matchesKeyword(Keyword.AWAIT) &&
_tokenMatchesKeyword(_peek(), Keyword.FOR)) {
Token awaitToken = _currentToken;
Statement statement = parseForStatement();
// ignore: deprecated_member_use_from_same_package
if (statement is! ForStatement) {
_reportErrorForToken(
CompileTimeErrorCode.ASYNC_FOR_IN_WRONG_CONTEXT, awaitToken);
}
return statement;
} else if (type == TokenType.SEMICOLON) {
return parseEmptyStatement();
} else if (isInitializedVariableDeclaration()) {
return parseVariableDeclarationStatementAfterMetadata(commentAndMetadata);
} else if (isFunctionDeclaration()) {
return parseFunctionDeclarationStatement();
} else if (type == TokenType.CLOSE_CURLY_BRACKET) {
_reportErrorForCurrentToken(ParserErrorCode.MISSING_STATEMENT);
return astFactory
.emptyStatement(_createSyntheticToken(TokenType.SEMICOLON));
} else {
return astFactory.expressionStatement(
parseExpression2(), _expect(TokenType.SEMICOLON));
}
}
/// Parse a normal formal parameter. Return the normal formal parameter that
/// was parsed.
///
/// normalFormalParameter ::=
/// functionSignature
/// | fieldFormalParameter
/// | simpleFormalParameter
///
/// functionSignature:
/// metadata returnType? identifier typeParameters? formalParameterList
///
/// fieldFormalParameter ::=
/// metadata finalConstVarOrType? 'this' '.' identifier
///
/// simpleFormalParameter ::=
/// declaredIdentifier
/// | metadata identifier
NormalFormalParameter parseNormalFormalParameter(
{bool inFunctionType: false}) {
Token covariantKeyword;
CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
if (_matchesKeyword(Keyword.COVARIANT)) {
// Check to ensure that 'covariant' isn't being used as the parameter name.
Token next = _peek();
if (_tokenMatchesKeyword(next, Keyword.FINAL) ||
_tokenMatchesKeyword(next, Keyword.CONST) ||
_tokenMatchesKeyword(next, Keyword.VAR) ||
_tokenMatchesKeyword(next, Keyword.THIS) ||
_tokenMatchesKeyword(next, Keyword.VOID) ||
_tokenMatchesIdentifier(next)) {
covariantKeyword = getAndAdvance();
}
}
FinalConstVarOrType holder = parseFinalConstVarOrType(!inFunctionType,
inFunctionType: inFunctionType);
Token thisKeyword = null;
Token period = null;
if (_matchesKeyword(Keyword.THIS)) {
thisKeyword = getAndAdvance();
period = _expect(TokenType.PERIOD);
}
if (!_matchesIdentifier() && inFunctionType) {
return astFactory.simpleFormalParameter2(
comment: commentAndMetadata.comment,
metadata: commentAndMetadata.metadata,
covariantKeyword: covariantKeyword,
keyword: holder.keyword,
type: holder.type,
identifier: null);
}
SimpleIdentifier identifier = parseSimpleIdentifier();
TypeParameterList typeParameters = _parseGenericMethodTypeParameters();
if (_matches(TokenType.OPEN_PAREN)) {
FormalParameterList parameters = _parseFormalParameterListUnchecked();
if (thisKeyword == null) {
if (holder.keyword != null) {
_reportErrorForToken(
ParserErrorCode.FUNCTION_TYPED_PARAMETER_VAR, holder.keyword);
}
return astFactory.functionTypedFormalParameter2(
comment: commentAndMetadata.comment,
metadata: commentAndMetadata.metadata,
covariantKeyword: covariantKeyword,
returnType: holder.type,
identifier: astFactory.simpleIdentifier(identifier.token,
isDeclaration: true),
typeParameters: typeParameters,
parameters: parameters);
} else {
return astFactory.fieldFormalParameter2(
comment: commentAndMetadata.comment,
metadata: commentAndMetadata.metadata,
covariantKeyword: covariantKeyword,
keyword: holder.keyword,
type: holder.type,
thisKeyword: thisKeyword,
period: period,
identifier: identifier,
typeParameters: typeParameters,
parameters: parameters);
}
} else if (typeParameters != null) {
// TODO(brianwilkerson) Report an error. It looks like a function-typed
// parameter with no parameter list.
//_reportErrorForToken(ParserErrorCode.MISSING_PARAMETERS, typeParameters.endToken);
}
TypeAnnotation type = holder.type;
if (type != null &&
holder.keyword != null &&
_tokenMatchesKeyword(holder.keyword, Keyword.VAR)) {
_reportErrorForToken(ParserErrorCode.VAR_AND_TYPE, holder.keyword);
}
if (thisKeyword != null) {
// TODO(brianwilkerson) If there are type parameters but no parameters,
// should we create a synthetic empty parameter list here so we can
// capture the type parameters?
return astFactory.fieldFormalParameter2(
comment: commentAndMetadata.comment,
metadata: commentAndMetadata.metadata,
covariantKeyword: covariantKeyword,
keyword: holder.keyword,
type: type,
thisKeyword: thisKeyword,
period: period,
identifier: identifier);
}
return astFactory.simpleFormalParameter2(
comment: commentAndMetadata.comment,
metadata: commentAndMetadata.metadata,
covariantKeyword: covariantKeyword,
keyword: holder.keyword,
type: type,
identifier:
astFactory.simpleIdentifier(identifier.token, isDeclaration: true));
}
/// Parse an operator declaration. The [commentAndMetadata] is the
/// documentation comment and metadata to be associated with the declaration.
/// The [externalKeyword] is the 'external' token. The [returnType] is the
/// return type that has already been parsed, or `null` if there was no return
/// type. Return the operator declaration that was parsed.
///
/// operatorDeclaration ::=
/// operatorSignature (';' | functionBody)
///
/// operatorSignature ::=
/// 'external'? returnType? 'operator' operator formalParameterList
MethodDeclaration parseOperator(CommentAndMetadata commentAndMetadata,
Token externalKeyword, TypeName returnType) {
Token operatorKeyword;
if (_matchesKeyword(Keyword.OPERATOR)) {
operatorKeyword = getAndAdvance();
} else {
_reportErrorForToken(
ParserErrorCode.MISSING_KEYWORD_OPERATOR, _currentToken);
operatorKeyword = _createSyntheticKeyword(Keyword.OPERATOR);
}
return _parseOperatorAfterKeyword(
commentAndMetadata, externalKeyword, returnType, operatorKeyword);
}
/// Parse a part or part-of directive. The [commentAndMetadata] is the
/// metadata to be associated with the directive. Return the part or part-of
/// directive that was parsed.
///
/// This method assumes that the current token matches `Keyword.PART`.
///
/// partDirective ::=
/// metadata 'part' stringLiteral ';'
///
/// partOfDirective ::=
/// metadata 'part' 'of' identifier ';'
Directive parsePartOrPartOfDirective(CommentAndMetadata commentAndMetadata) {
if (_tokenMatchesKeyword(_peek(), Keyword.OF)) {
return _parsePartOfDirective(commentAndMetadata);
}
return _parsePartDirective(commentAndMetadata);
}
/// Parse a postfix expression. Return the postfix expression that was parsed.
///
/// postfixExpression ::=
/// assignableExpression postfixOperator
/// | primary selector*
///
/// selector ::=
/// assignableSelector
/// | argumentPart
Expression parsePostfixExpression() {
Expression operand = parseAssignableExpression(true);
TokenType type = _currentToken.type;
if (type == TokenType.OPEN_SQUARE_BRACKET ||
type == TokenType.PERIOD ||
type == TokenType.QUESTION_PERIOD ||
type == TokenType.OPEN_PAREN ||
type == TokenType.LT ||
type == TokenType.INDEX) {
do {
if (_isLikelyArgumentList()) {
TypeArgumentList typeArguments = _parseOptionalTypeArguments();
ArgumentList argumentList = parseArgumentList();
Expression currentOperand = operand;
if (currentOperand is PropertyAccess) {
operand = astFactory.methodInvocation(
currentOperand.target,
currentOperand.operator,
currentOperand.propertyName,
typeArguments,
argumentList);
} else {
operand = astFactory.functionExpressionInvocation(
operand, typeArguments, argumentList);
}
} else if (enableOptionalNewAndConst &&
operand is Identifier &&
_isLikelyNamedInstanceCreation()) {
TypeArgumentList typeArguments = _parseOptionalTypeArguments();
Token period = _expect(TokenType.PERIOD);
SimpleIdentifier name = parseSimpleIdentifier();
ArgumentList argumentList = parseArgumentList();
TypeName typeName = astFactory.typeName(operand, typeArguments);
operand = astFactory.instanceCreationExpression(null,
astFactory.constructorName(typeName, period, name), argumentList);
} else {
operand = parseAssignableSelector(operand, true);
}
type = _currentToken.type;
} while (type == TokenType.OPEN_SQUARE_BRACKET ||
type == TokenType.PERIOD ||
type == TokenType.QUESTION_PERIOD ||
type == TokenType.OPEN_PAREN ||
type == TokenType.INDEX);
return operand;
}
if (!_currentToken.type.isIncrementOperator) {
return operand;
}
_ensureAssignable(operand);
Token operator = getAndAdvance();
return astFactory.postfixExpression(operand, operator);
}
/// Parse a prefixed identifier. Return the prefixed identifier that was
/// parsed.
///
/// prefixedIdentifier ::=
/// identifier ('.' identifier)?
Identifier parsePrefixedIdentifier() {
return _parsePrefixedIdentifierAfterIdentifier(parseSimpleIdentifier());
}
/// Parse a primary expression. Return the primary expression that was parsed.
///
/// primary ::=
/// thisExpression
/// | 'super' unconditionalAssignableSelector
/// | functionExpression
/// | literal
/// | identifier
/// | newExpression
/// | constObjectExpression
/// | '(' expression ')'
/// | argumentDefinitionTest
///
/// literal ::=
/// nullLiteral
/// | booleanLiteral
/// | numericLiteral
/// | stringLiteral
/// | symbolLiteral
/// | mapLiteral
/// | listLiteral
Expression parsePrimaryExpression() {
if (_matchesIdentifier()) {
// TODO(brianwilkerson) The code below was an attempt to recover from an
// error case, but it needs to be applied as a recovery only after we
// know that parsing it as an identifier doesn't work. Leaving the code as
// a reminder of how to recover.
// if (isFunctionExpression(_peek())) {
// //
// // Function expressions were allowed to have names at one point, but this is now illegal.
// //
// reportError(ParserErrorCode.NAMED_FUNCTION_EXPRESSION, getAndAdvance());
// return parseFunctionExpression();
// }
return _parsePrefixedIdentifierUnchecked();
}
TokenType type = _currentToken.type;
if (type == TokenType.STRING) {
return parseStringLiteral();
} else if (type == TokenType.INT) {
Token token = getAndAdvance();
int value = null;
try {
value = int.parse(token.lexeme);
} on FormatException {
// The invalid format should have been reported by the scanner.
}
return astFactory.integerLiteral(token, value);
}
Keyword keyword = _currentToken.keyword;
if (keyword == Keyword.NULL) {
return astFactory.nullLiteral(getAndAdvance());
} else if (keyword == Keyword.NEW) {
return parseNewExpression();
} else if (keyword == Keyword.THIS) {
return astFactory.thisExpression(getAndAdvance());
} else if (keyword == Keyword.SUPER) {
return parseAssignableSelector(
astFactory.superExpression(getAndAdvance()), false,
allowConditional: false);
} else if (keyword == Keyword.FALSE) {
return astFactory.booleanLiteral(getAndAdvance(), false);
} else if (keyword == Keyword.TRUE) {
return astFactory.booleanLiteral(getAndAdvance(), true);
}
if (type == TokenType.DOUBLE) {
Token token = getAndAdvance();
double value = 0.0;
try {
value = double.parse(token.lexeme);
} on FormatException {
// The invalid format should have been reported by the scanner.
}
return astFactory.doubleLiteral(token, value);
} else if (type == TokenType.HEXADECIMAL) {
Token token = getAndAdvance();
int value = null;
try {
value = int.parse(token.lexeme);
} on FormatException {
// The invalid format should have been reported by the scanner.
}
return astFactory.integerLiteral(token, value);
} else if (keyword == Keyword.CONST) {
return parseConstExpression();
} else if (type == TokenType.OPEN_PAREN) {
if (isFunctionExpression(_currentToken)) {
return parseFunctionExpression();
}
Token leftParenthesis = getAndAdvance();
bool wasInInitializer = _inInitializer;
_inInitializer = false;
try {
Expression expression = parseExpression2();
Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
return astFactory.parenthesizedExpression(
leftParenthesis, expression, rightParenthesis);
} finally {
_inInitializer = wasInInitializer;
}
} else if (type == TokenType.LT) {
if (isFunctionExpression(currentToken)) {
return parseFunctionExpression();
}
return parseListOrMapLiteral(null);
} else if (type == TokenType.OPEN_CURLY_BRACKET) {
return parseMapLiteral(null, null);
} else if (type == TokenType.OPEN_SQUARE_BRACKET ||
type == TokenType.INDEX) {
return parseListLiteral(null, null);
} else if (type == TokenType.QUESTION &&
_tokenMatches(_peek(), TokenType.IDENTIFIER)) {
_reportErrorForCurrentToken(
ParserErrorCode.UNEXPECTED_TOKEN, [_currentToken.lexeme]);
_advance();
return parsePrimaryExpression();
} else if (keyword == Keyword.VOID) {
//
// Recover from having a return type of "void" where a return type is not
// expected.
//
// TODO(brianwilkerson) Improve this error message.
_reportErrorForCurrentToken(
ParserErrorCode.UNEXPECTED_TOKEN, [_currentToken.lexeme]);
_advance();
return parsePrimaryExpression();
} else if (type == TokenType.HASH) {
return parseSymbolLiteral();
} else {
_reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
return createSyntheticIdentifier();
}
}
/// Parse a redirecting constructor invocation. The flag [hasPeriod] should be
/// `true` if the `this` is followed by a period. Return the redirecting
/// constructor invocation that was parsed.
///
/// This method assumes that the current token matches `Keyword.THIS`.
///
/// redirectingConstructorInvocation ::=
/// 'this' ('.' identifier)? arguments
RedirectingConstructorInvocation parseRedirectingConstructorInvocation(
bool hasPeriod) {
Token keyword = getAndAdvance();
Token period = null;
SimpleIdentifier constructorName = null;
if (hasPeriod) {
period = getAndAdvance();
if (_matchesIdentifier()) {
constructorName = _parseSimpleIdentifierUnchecked(isDeclaration: false);
} else {
_reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
constructorName = createSyntheticIdentifier(isDeclaration: false);
_advance();
}
}
ArgumentList argumentList = _parseArgumentListChecked();
return astFactory.redirectingConstructorInvocation(
keyword, period, constructorName, argumentList);
}
/// Parse a relational expression. Return the relational expression that was
/// parsed.
///
/// relationalExpression ::=
/// bitwiseOrExpression ('is' '!'? type | 'as' type | relationalOperator bitwiseOrExpression)?
/// | 'super' relationalOperator bitwiseOrExpression
Expression parseRelationalExpression() {
if (_currentToken.keyword == Keyword.SUPER &&
_currentToken.next.type.isRelationalOperator) {
Expression expression = astFactory.superExpression(getAndAdvance());
Token operator = getAndAdvance();
return astFactory.binaryExpression(
expression, operator, parseBitwiseOrExpression());
}
Expression expression = parseBitwiseOrExpression();
Keyword keyword = _currentToken.keyword;
if (keyword == Keyword.AS) {
Token asOperator = getAndAdvance();
return astFactory.asExpression(
expression, asOperator, parseTypeNotVoid(true));
} else if (keyword == Keyword.IS) {
Token isOperator = getAndAdvance();
Token notOperator = null;
if (_matches(TokenType.BANG)) {
notOperator = getAndAdvance();
}
TypeAnnotation type = parseTypeNotVoid(true);
return astFactory.isExpression(expression, isOperator, notOperator, type);
} else if (_currentToken.type.isRelationalOperator) {
Token operator = getAndAdvance();
return astFactory.binaryExpression(
expression, operator, parseBitwiseOrExpression());
}
return expression;
}
/// Parse a rethrow expression. Return the rethrow expression that was parsed.
///
/// This method assumes that the current token matches `Keyword.RETHROW`.
///
/// rethrowExpression ::=
/// 'rethrow'
Expression parseRethrowExpression() =>
astFactory.rethrowExpression(getAndAdvance());
/// Parse a return statement. Return the return statement that was parsed.
///
/// This method assumes that the current token matches `Keyword.RETURN`.
///
/// returnStatement ::=
/// 'return' expression? ';'
Statement parseReturnStatement() {
Token returnKeyword = getAndAdvance();
if (_matches(TokenType.SEMICOLON)) {
return astFactory.returnStatement(returnKeyword, null, getAndAdvance());
}
Expression expression = parseExpression2();
Token semicolon = _expect(TokenType.SEMICOLON);
return astFactory.returnStatement(returnKeyword, expression, semicolon);
}
/// Parse a setter. The [commentAndMetadata] is the documentation comment and
/// metadata to be associated with the declaration. The [externalKeyword] is
/// the 'external' token. The [staticKeyword] is the static keyword, or `null`
/// if the setter is not static. The [returnType] is the return type that has
/// already been parsed, or `null` if there was no return type. Return the
/// setter that was parsed.
///
/// This method assumes that the current token matches `Keyword.SET`.
///
/// setter ::=
/// setterSignature functionBody?
///
/// setterSignature ::=
/// 'external'? 'static'? returnType? 'set' identifier formalParameterList
MethodDeclaration parseSetter(CommentAndMetadata commentAndMetadata,
Token externalKeyword, Token staticKeyword, TypeAnnotation returnType) {
Token propertyKeyword = getAndAdvance();
SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
FormalParameterList parameters = parseFormalParameterList();
_validateFormalParameterList(parameters);
FunctionBody body = parseFunctionBody(
externalKeyword != null || staticKeyword == null,
ParserErrorCode.STATIC_SETTER_WITHOUT_BODY,
false);
if (externalKeyword != null && body is! EmptyFunctionBody) {
_reportErrorForCurrentToken(ParserErrorCode.EXTERNAL_SETTER_WITH_BODY);
}
return astFactory.methodDeclaration(
commentAndMetadata.comment,
commentAndMetadata.metadata,
externalKeyword,
staticKeyword,
returnType,
propertyKeyword,
null,
name,
null,
parameters,
body);
}
/// Parse a shift expression. Return the shift expression that was parsed.
///
/// shiftExpression ::=
/// additiveExpression (shiftOperator additiveExpression)*
/// | 'super' (shiftOperator additiveExpression)+
Expression parseShiftExpression() {
Expression expression;
if (_currentToken.keyword == Keyword.SUPER &&
_currentToken.next.type.isShiftOperator) {
expression = astFactory.superExpression(getAndAdvance());
} else {
expression = parseAdditiveExpression();
}
while (_currentToken.type.isShiftOperator) {
expression = astFactory.binaryExpression(
expression, getAndAdvance(), parseAdditiveExpression());
}
return expression;
}
/// Parse a simple identifier. Return the simple identifier that was parsed.
///
/// identifier ::=
/// IDENTIFIER
SimpleIdentifier parseSimpleIdentifier(
{bool allowKeyword: false, bool isDeclaration: false}) {
if (_matchesIdentifier() ||
(allowKeyword && _tokenMatchesIdentifierOrKeyword(_currentToken))) {
return _parseSimpleIdentifierUnchecked(isDeclaration: isDeclaration);
}
_reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
return createSyntheticIdentifier(isDeclaration: isDeclaration);
}
/// Parse a statement, starting with the given [token]. Return the statement
/// that was parsed, or `null` if the tokens do not represent a recognizable
/// statement.
Statement parseStatement(Token token) {
_currentToken = token;
return parseStatement2();
}
/// Parse a statement. Return the statement that was parsed.
///
/// statement ::=
/// label* nonLabeledStatement
Statement parseStatement2() {
if (_treeDepth > _MAX_TREE_DEPTH) {
throw new _TooDeepTreeError();
}
_treeDepth++;
try {
List<Label> labels = null;
while (
_matchesIdentifier() && _currentToken.next.type == TokenType.COLON) {
Label label = parseLabel(isDeclaration: true);
if (labels == null) {
labels = <Label>[label];
} else {
labels.add(label);
}
}
Statement statement = parseNonLabeledStatement();
if (labels == null) {
return statement;
}
return astFactory.labeledStatement(labels, statement);
} finally {
_treeDepth--;
}
}
/// Parse a sequence of statements, starting with the given [token]. Return
/// the statements that were parsed, or `null` if the tokens do not represent
/// a recognizable sequence of statements.
List<Statement> parseStatements(Token token) {
_currentToken = token;
return _parseStatementList();
}
/// Parse a string literal. Return the string literal that was parsed.
///
/// stringLiteral ::=
/// MULTI_LINE_STRING+
/// | SINGLE_LINE_STRING+
StringLiteral parseStringLiteral() {
if (_matches(TokenType.STRING)) {
return _parseStringLiteralUnchecked();
}
_reportErrorForCurrentToken(ParserErrorCode.EXPECTED_STRING_LITERAL);
return createSyntheticStringLiteral();
}
/// Parse a super constructor invocation. Return the super constructor
/// invocation that was parsed.
///
/// This method assumes that the current token matches [Keyword.SUPER].
///
/// superConstructorInvocation ::=
/// 'super' ('.' identifier)? arguments
SuperConstructorInvocation parseSuperConstructorInvocation() {
Token keyword = getAndAdvance();
Token period = null;
SimpleIdentifier constructorName = null;
if (_matches(TokenType.PERIOD)) {
period = getAndAdvance();
constructorName = parseSimpleIdentifier();
}
ArgumentList argumentList = _parseArgumentListChecked();
return astFactory.superConstructorInvocation(
keyword, period, constructorName, argumentList);
}
/// Parse a switch statement. Return the switch statement that was parsed.
///
/// switchStatement ::=
/// 'switch' '(' expression ')' '{' switchCase* defaultCase? '}'
///
/// switchCase ::=
/// label* ('case' expression ':') statements
///
/// defaultCase ::=
/// label* 'default' ':' statements
SwitchStatement parseSwitchStatement() {
bool wasInSwitch = _inSwitch;
_inSwitch = true;
try {
HashSet<String> definedLabels = new HashSet<String>();
Token keyword = _expectKeyword(Keyword.SWITCH);
Token leftParenthesis = _expect(TokenType.OPEN_PAREN);
Expression expression = parseExpression2();
Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
Token leftBracket = _expect(TokenType.OPEN_CURLY_BRACKET);
Token defaultKeyword = null;
List<SwitchMember> members = <SwitchMember>[];
TokenType type = _currentToken.type;
while (type != TokenType.EOF && type != TokenType.CLOSE_CURLY_BRACKET) {
List<Label> labels = <Label>[];
while (
_matchesIdentifier() && _tokenMatches(_peek(), TokenType.COLON)) {
SimpleIdentifier identifier =
_parseSimpleIdentifierUnchecked(isDeclaration: true);
String label = identifier.token.lexeme;
if (definedLabels.contains(label)) {
_reportErrorForToken(
ParserErrorCode.DUPLICATE_LABEL_IN_SWITCH_STATEMENT,
identifier.token,
[label]);
} else {
definedLabels.add(label);
}
Token colon = getAndAdvance();
labels.add(astFactory.label(identifier, colon));
}
Keyword keyword = _currentToken.keyword;
if (keyword == Keyword.CASE) {
Token caseKeyword = getAndAdvance();
Expression caseExpression = parseExpression2();
Token colon = _expect(TokenType.COLON);
members.add(astFactory.switchCase(labels, caseKeyword, caseExpression,
colon, _parseStatementList()));
if (defaultKeyword != null) {
_reportErrorForToken(
ParserErrorCode.SWITCH_HAS_CASE_AFTER_DEFAULT_CASE,
caseKeyword);
}
} else if (keyword == Keyword.DEFAULT) {
if (defaultKeyword != null) {
_reportErrorForToken(
ParserErrorCode.SWITCH_HAS_MULTIPLE_DEFAULT_CASES, _peek());
}
defaultKeyword = getAndAdvance();
Token colon = _expect(TokenType.COLON);
members.add(astFactory.switchDefault(
labels, defaultKeyword, colon, _parseStatementList()));
} else {
// We need to advance, otherwise we could end up in an infinite loop,
// but this could be a lot smarter about recovering from the error.
_reportErrorForCurrentToken(ParserErrorCode.EXPECTED_CASE_OR_DEFAULT);
bool atEndOrNextMember() {
TokenType type = _currentToken.type;
if (type == TokenType.EOF ||
type == TokenType.CLOSE_CURLY_BRACKET) {
return true;
}
Keyword keyword = _currentToken.keyword;
return keyword == Keyword.CASE || keyword == Keyword.DEFAULT;
}
while (!atEndOrNextMember()) {
_advance();
}
}
type = _currentToken.type;
}
Token rightBracket = _expect(TokenType.CLOSE_CURLY_BRACKET);
return astFactory.switchStatement(keyword, leftParenthesis, expression,
rightParenthesis, leftBracket, members, rightBracket);
} finally {
_inSwitch = wasInSwitch;
}
}
/// Parse a symbol literal. Return the symbol literal that was parsed.
///
/// This method assumes that the current token matches [TokenType.HASH].
///
/// symbolLiteral ::=
/// '#' identifier ('.' identifier)*
SymbolLiteral parseSymbolLiteral() {
Token poundSign = getAndAdvance();
List<Token> components = <Token>[];
if (_matchesIdentifier()) {
components.add(getAndAdvance());
while (_optional(TokenType.PERIOD)) {
if (_matchesIdentifier()) {
components.add(getAndAdvance());
} else {
_reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
components.add(_createSyntheticToken(TokenType.IDENTIFIER));
break;
}
}
} else if (_currentToken.isOperator) {
components.add(getAndAdvance());
} else if (_matchesKeyword(Keyword.VOID)) {
components.add(getAndAdvance());
} else {
_reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
components.add(_createSyntheticToken(TokenType.IDENTIFIER));
}
return astFactory.symbolLiteral(poundSign, components);
}
/// Parse a throw expression. Return the throw expression that was parsed.
///
/// This method assumes that the current token matches [Keyword.THROW].
///
/// throwExpression ::=
/// 'throw' expression
Expression parseThrowExpression() {
Token keyword = getAndAdvance();
TokenType type = _currentToken.type;
if (type == TokenType.SEMICOLON || type == TokenType.CLOSE_PAREN) {
_reportErrorForToken(
ParserErrorCode.MISSING_EXPRESSION_IN_THROW, _currentToken);
return astFactory.throwExpression(keyword, createSyntheticIdentifier());
}
Expression expression = parseExpression2();
return astFactory.throwExpression(keyword, expression);
}
/// Parse a throw expression. Return the throw expression that was parsed.
///
/// This method assumes that the current token matches [Keyword.THROW].
///
/// throwExpressionWithoutCascade ::=
/// 'throw' expressionWithoutCascade
Expression parseThrowExpressionWithoutCascade() {
Token keyword = getAndAdvance();
TokenType type = _currentToken.type;
if (type == TokenType.SEMICOLON || type == TokenType.CLOSE_PAREN) {
_reportErrorForToken(
ParserErrorCode.MISSING_EXPRESSION_IN_THROW, _currentToken);
return astFactory.throwExpression(keyword, createSyntheticIdentifier());
}
Expression expression = parseExpressionWithoutCascade();
return astFactory.throwExpression(keyword, expression);
}
/// Parse a try statement. Return the try statement that was parsed.
///
/// This method assumes that the current token matches [Keyword.TRY].
///
/// tryStatement ::=
/// 'try' block (onPart+ finallyPart? | finallyPart)
///
/// onPart ::=
/// catchPart block
/// | 'on' type catchPart? block
///
/// catchPart ::=
/// 'catch' '(' identifier (',' identifier)? ')'
///
/// finallyPart ::=
/// 'finally' block
Statement parseTryStatement() {
Token tryKeyword = getAndAdvance();
Block body = _parseBlockChecked();
List<CatchClause> catchClauses = <CatchClause>[];
Block finallyClause = null;
while (_matchesKeyword(Keyword.ON) || _matchesKeyword(Keyword.CATCH)) {
Token onKeyword = null;
TypeName exceptionType = null;
if (_matchesKeyword(Keyword.ON)) {
onKeyword = getAndAdvance();
exceptionType = parseTypeNotVoid(false);
}
Token catchKeyword = null;
Token leftParenthesis = null;
SimpleIdentifier exceptionParameter = null;
Token comma = null;
SimpleIdentifier stackTraceParameter = null;
Token rightParenthesis = null;
if (_matchesKeyword(Keyword.CATCH)) {
catchKeyword = getAndAdvance();
leftParenthesis = _expect(TokenType.OPEN_PAREN);
exceptionParameter = parseSimpleIdentifier(isDeclaration: true);
if (_matches(TokenType.COMMA)) {
comma = getAndAdvance();
stackTraceParameter = parseSimpleIdentifier(isDeclaration: true);
}
rightParenthesis = _expect(TokenType.CLOSE_PAREN);
}
Block catchBody = _parseBlockChecked();
catchClauses.add(astFactory.catchClause(
onKeyword,
exceptionType,
catchKeyword,
leftParenthesis,
exceptionParameter,
comma,
stackTraceParameter,
rightParenthesis,
catchBody));
}
Token finallyKeyword = null;
if (_matchesKeyword(Keyword.FINALLY)) {
finallyKeyword = getAndAdvance();
finallyClause = _parseBlockChecked();
} else if (catchClauses.isEmpty) {
_reportErrorForCurrentToken(ParserErrorCode.MISSING_CATCH_OR_FINALLY);
}
return astFactory.tryStatement(
tryKeyword, body, catchClauses, finallyKeyword, finallyClause);
}
/// Parse a type alias. The [commentAndMetadata] is the metadata to be
/// associated with the member. Return the type alias that was parsed.
///
/// This method assumes that the current token matches [Keyword.TYPEDEF].
///
/// typeAlias ::=
/// 'typedef' typeAliasBody
/// | genericTypeAlias
///
/// typeAliasBody ::=
/// functionTypeAlias
///
/// functionTypeAlias ::=
/// functionPrefix typeParameterList? formalParameterList ';'
///
/// functionPrefix ::=
/// returnType? name
TypeAlias parseTypeAlias(CommentAndMetadata commentAndMetadata) {
Token keyword = getAndAdvance();
if (_matchesIdentifier()) {
Token next = _peek();
if (_tokenMatches(next, TokenType.LT)) {
next = _skipTypeParameterList(next);
if (next != null && _tokenMatches(next, TokenType.EQ)) {
TypeAlias typeAlias =
parseGenericTypeAlias(commentAndMetadata, keyword);
return typeAlias;
}
} else if (_tokenMatches(next, TokenType.EQ)) {
TypeAlias typeAlias =
parseGenericTypeAlias(commentAndMetadata, keyword);
return typeAlias;
}
}
return _parseFunctionTypeAlias(commentAndMetadata, keyword);
}
/// Parse a type.
///
/// type ::=
/// typeWithoutFunction
/// | functionType
TypeAnnotation parseTypeAnnotation(bool inExpression) {
TypeAnnotation type = null;
if (_atGenericFunctionTypeAfterReturnType(_currentToken)) {
// Generic function type with no return type.
type = parseGenericFunctionTypeAfterReturnType(null);
} else {
type = parseTypeWithoutFunction(inExpression);
}
while (_atGenericFunctionTypeAfterReturnType(_currentToken)) {
type = parseGenericFunctionTypeAfterReturnType(type);
}
return type;
}
/// Parse a list of type arguments. Return the type argument list that was
/// parsed.
///
/// This method assumes that the current token matches `TokenType.LT`.
///
/// typeArguments ::=
/// '<' typeList '>'
///
/// typeList ::=
/// type (',' type)*
TypeArgumentList parseTypeArgumentList() {
Token leftBracket = getAndAdvance();
List<TypeAnnotation> arguments = <TypeAnnotation>[
parseTypeAnnotation(false)
];
while (_optional(TokenType.COMMA)) {
arguments.add(parseTypeAnnotation(false));
}
Token rightBracket = _expectGt();
return astFactory.typeArgumentList(leftBracket, arguments, rightBracket);
}
/// Parse a type which is not void and is not a function type. Return the type
/// that was parsed.
///
/// typeNotVoidWithoutFunction ::=
/// qualified typeArguments?
// TODO(eernst): Rename this to `parseTypeNotVoidWithoutFunction`?
// Apparently, it was named `parseTypeName` before type arguments existed.
TypeName parseTypeName(bool inExpression) {
return _parseTypeName(inExpression);
}
/// Parse a type which is not `void`.
///
/// typeNotVoid ::=
/// functionType
/// | typeNotVoidWithoutFunction
TypeAnnotation parseTypeNotVoid(bool inExpression) {
TypeAnnotation type = null;
if (_atGenericFunctionTypeAfterReturnType(_currentToken)) {
// Generic function type with no return type.
type = parseGenericFunctionTypeAfterReturnType(null);
} else if (_currentToken.keyword == Keyword.VOID &&
_atGenericFunctionTypeAfterReturnType(_currentToken.next)) {
type = astFactory.typeName(
astFactory.simpleIdentifier(getAndAdvance()), null);
} else {
type = parseTypeName(inExpression);
}
while (_atGenericFunctionTypeAfterReturnType(_currentToken)) {
type = parseGenericFunctionTypeAfterReturnType(type);
}
return type;
}
/// Parse a type parameter. Return the type parameter that was parsed.
///
/// typeParameter ::=
/// metadata name ('extends' bound)?
TypeParameter parseTypeParameter() {
CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
if (_matchesKeyword(Keyword.EXTENDS)) {
Token keyword = getAndAdvance();
TypeAnnotation bound = parseTypeNotVoid(false);
return astFactory.typeParameter(commentAndMetadata.comment,
commentAndMetadata.metadata, name, keyword, bound);
}
return astFactory.typeParameter(commentAndMetadata.comment,
commentAndMetadata.metadata, name, null, null);
}
/// Parse a list of type parameters. Return the list of type parameters that
/// were parsed.
///
/// This method assumes that the current token matches `TokenType.LT`.
///
/// typeParameterList ::=
/// '<' typeParameter (',' typeParameter)* '>'
TypeParameterList parseTypeParameterList() {
Token leftBracket = getAndAdvance();
List<TypeParameter> typeParameters = <TypeParameter>[parseTypeParameter()];
while (_optional(TokenType.COMMA)) {
typeParameters.add(parseTypeParameter());
}
Token rightBracket = _expectGt();
return astFactory.typeParameterList(
leftBracket, typeParameters, rightBracket);
}
/// Parse a type which is not a function type.
///
/// typeWithoutFunction ::=
/// `void`
/// | typeNotVoidWithoutFunction
TypeAnnotation parseTypeWithoutFunction(bool inExpression) {
if (_currentToken.keyword == Keyword.VOID) {
return astFactory.typeName(
astFactory.simpleIdentifier(getAndAdvance()), null);
} else {
return parseTypeName(inExpression);
}
}
/// Parse a unary expression. Return the unary expression that was parsed.
///
/// unaryExpression ::=
/// prefixOperator unaryExpression
/// | awaitExpression
/// | postfixExpression
/// | unaryOperator 'super'
/// | '-' 'super'
/// | incrementOperator assignableExpression
Expression parseUnaryExpression() {
TokenType type = _currentToken.type;
if (type == TokenType.MINUS ||
type == TokenType.BANG ||
type == TokenType.TILDE) {
Token operator = getAndAdvance();
if (_matchesKeyword(Keyword.SUPER)) {
TokenType nextType = _peek().type;
if (nextType == TokenType.OPEN_SQUARE_BRACKET ||
nextType == TokenType.PERIOD) {
// "prefixOperator unaryExpression"
// --> "prefixOperator postfixExpression"
// --> "prefixOperator primary selector*"
// --> "prefixOperator 'super' assignableSelector selector*"
return astFactory.prefixExpression(operator, parseUnaryExpression());
}
return astFactory.prefixExpression(
operator, astFactory.superExpression(getAndAdvance()));
}
return astFactory.prefixExpression(operator, parseUnaryExpression());
} else if (_currentToken.type.isIncrementOperator) {
Token operator = getAndAdvance();
if (_matchesKeyword(Keyword.SUPER)) {
TokenType nextType = _peek().type;
if (nextType == TokenType.OPEN_SQUARE_BRACKET ||
nextType == TokenType.PERIOD) {
// --> "prefixOperator 'super' assignableSelector selector*"
return astFactory.prefixExpression(operator, parseUnaryExpression());
}
//
// Even though it is not valid to use an incrementing operator
// ('++' or '--') before 'super', we can (and therefore must) interpret
// "--super" as semantically equivalent to "-(-super)". Unfortunately,
// we cannot do the same for "++super" because "+super" is also not
// valid.
//
if (type == TokenType.MINUS_MINUS) {
Token firstOperator = _createToken(operator, TokenType.MINUS);
Token secondOperator =
new Token(TokenType.MINUS, operator.offset + 1);
secondOperator.setNext(_currentToken);
firstOperator.setNext(secondOperator);
operator.previous.setNext(firstOperator);
return astFactory.prefixExpression(
firstOperator,
astFactory.prefixExpression(
secondOperator, astFactory.superExpression(getAndAdvance())));
}
// Invalid operator before 'super'
_reportErrorForCurrentToken(
ParserErrorCode.INVALID_OPERATOR_FOR_SUPER, [operator.lexeme]);
return astFactory.prefixExpression(
operator, astFactory.superExpression(getAndAdvance()));
}
return astFactory.prefixExpression(
operator, parseAssignableExpression(false));
} else if (type == TokenType.PLUS) {
_reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
return createSyntheticIdentifier();
} else if (_inAsync && _matchesKeyword(Keyword.AWAIT)) {
return parseAwaitExpression();
}
return parsePostfixExpression();
}
/// Parse a variable declaration. Return the variable declaration that was
/// parsed.
///
/// variableDeclaration ::=
/// identifier ('=' expression)?
VariableDeclaration parseVariableDeclaration() {
// TODO(paulberry): prior to the fix for bug 23204, we permitted
// annotations before variable declarations (e.g. "String @deprecated s;").
// Although such constructions are prohibited by the spec, we may want to
// consider handling them anyway to allow for better parser recovery in the
// event that the user erroneously tries to use them. However, as a
// counterargument, this would likely degrade parser recovery in the event
// of a construct like "class C { int @deprecated foo() {} }" (i.e. the
// user is in the middle of inserting "int bar;" prior to
// "@deprecated foo() {}").
SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
Token equals = null;
Expression initializer = null;
if (_matches(TokenType.EQ)) {
equals = getAndAdvance();
initializer = parseExpression2();
}
return astFactory.variableDeclaration(name, equals, initializer);
}
/// Parse a variable declaration list. The [commentAndMetadata] is the
/// metadata to be associated with the variable declaration list. Return the
/// variable declaration list that was parsed.
///
/// variableDeclarationList ::=
/// finalConstVarOrType variableDeclaration (',' variableDeclaration)*
VariableDeclarationList parseVariableDeclarationListAfterMetadata(
CommentAndMetadata commentAndMetadata) {
FinalConstVarOrType holder = parseFinalConstVarOrType(false);
return parseVariableDeclarationListAfterType(
commentAndMetadata, holder.keyword, holder.type);
}
/// Parse a variable declaration list. The [commentAndMetadata] is the
/// metadata to be associated with the variable declaration list, or `null`
/// if there is no attempt at parsing the comment and metadata. The [keyword]
/// is the token representing the 'final', 'const' or 'var' keyword, or
/// `null` if there is no keyword. The [type] is the type of the variables in
/// the list. Return the variable declaration list that was parsed.
///
/// variableDeclarationList ::=
/// finalConstVarOrType variableDeclaration (',' variableDeclaration)*
VariableDeclarationList parseVariableDeclarationListAfterType(
CommentAndMetadata commentAndMetadata,
Token keyword,
TypeAnnotation type) {
if (type != null &&
keyword != null &&
_tokenMatchesKeyword(keyword, Keyword.VAR)) {
_reportErrorForToken(ParserErrorCode.VAR_AND_TYPE, keyword);
}
List<VariableDeclaration> variables = <VariableDeclaration>[
parseVariableDeclaration()
];
while (_optional(TokenType.COMMA)) {
variables.add(parseVariableDeclaration());
}
return astFactory.variableDeclarationList(commentAndMetadata?.comment,
commentAndMetadata?.metadata, keyword, type, variables);
}
/// Parse a variable declaration statement. The [commentAndMetadata] is the
/// metadata to be associated with the variable declaration statement, or
/// `null` if there is no attempt at parsing the comment and metadata. Return
/// the variable declaration statement that was parsed.
///
/// variableDeclarationStatement ::=
/// variableDeclarationList ';'
VariableDeclarationStatement parseVariableDeclarationStatementAfterMetadata(
CommentAndMetadata commentAndMetadata) {
// Token startToken = currentToken;
VariableDeclarationList variableList =
parseVariableDeclarationListAfterMetadata(commentAndMetadata);
// if (!matches(TokenType.SEMICOLON)) {
// if (matches(startToken, Keyword.VAR) && isTypedIdentifier(startToken.getNext())) {
// // TODO(brianwilkerson) This appears to be of the form "var type variable". We should do
// // a better job of recovering in this case.
// }
// }
Token semicolon = _expect(TokenType.SEMICOLON);
return astFactory.variableDeclarationStatement(variableList, semicolon);
}
/// Parse a while statement. Return the while statement that was parsed.
///
/// This method assumes that the current token matches [Keyword.WHILE].
///
/// whileStatement ::=
/// 'while' '(' expression ')' statement
Statement parseWhileStatement() {
bool wasInLoop = _inLoop;
_inLoop = true;
try {
Token keyword = getAndAdvance();
Token leftParenthesis = _expect(TokenType.OPEN_PAREN);
Expression condition = parseExpression2();
Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
Statement body = parseStatement2();
return astFactory.whileStatement(
keyword, leftParenthesis, condition, rightParenthesis, body);
} finally {
_inLoop = wasInLoop;
}
}
/// Parse a with clause. Return the with clause that was parsed.
///
/// This method assumes that the current token matches `Keyword.WITH`.
///
/// withClause ::=
/// 'with' typeName (',' typeName)*
WithClause parseWithClause() {
Token withKeyword = getAndAdvance();
List<TypeName> types = <TypeName>[];
do {
TypeName typeName = parseTypeName(false);
types.add(typeName);
} while (_optional(TokenType.COMMA));
return astFactory.withClause(withKeyword, types);
}
/// Parse a yield statement. Return the yield statement that was parsed.
///
/// This method assumes that the current token matches [Keyword.YIELD].
///
/// yieldStatement ::=
/// 'yield' '*'? expression ';'
YieldStatement parseYieldStatement() {
Token yieldToken = getAndAdvance();
Token star = null;
if (_matches(TokenType.STAR)) {
star = getAndAdvance();
}
Expression expression = parseExpression2();
Token semicolon = _expect(TokenType.SEMICOLON);
return astFactory.yieldStatement(yieldToken, star, expression, semicolon);
}
/// Parse a formal parameter list, starting at the [startToken], without
/// actually creating a formal parameter list or changing the current token.
/// Return the token following the parameter list that was parsed, or `null`
/// if the given token is not the first token in a valid parameter list.
///
/// This method must be kept in sync with [parseFormalParameterList].
Token skipFormalParameterList(Token startToken) {
if (!_tokenMatches(startToken, TokenType.OPEN_PAREN)) {
return null;
}
return (startToken as BeginToken).endToken?.next;
}
/// Parse the portion of a generic function type after the return type,
/// starting at the [startToken], without actually creating a generic function
/// type or changing the current token. Return the token following the generic
/// function type that was parsed, or `null` if the given token is not the
/// first token in a valid generic function type.
///
/// This method must be kept in sync with
/// [parseGenericFunctionTypeAfterReturnType].
Token skipGenericFunctionTypeAfterReturnType(Token startToken) {
Token next = startToken.next; // Skip 'Function'
if (_tokenMatches(next, TokenType.LT)) {
next = skipTypeParameterList(next);
if (next == null) {
return null;
}
}
return skipFormalParameterList(next);
}
/// Parse a prefixed identifier, starting at the [startToken], without
/// actually creating a prefixed identifier or changing the current token.
/// Return the token following the prefixed identifier that was parsed, or
/// `null` if the given token is not the first token in a valid prefixed
/// identifier.
///
/// This method must be kept in sync with [parsePrefixedIdentifier].
///
/// prefixedIdentifier ::=
/// identifier ('.' identifier)?
Token skipPrefixedIdentifier(Token startToken) {
Token token = skipSimpleIdentifier(startToken);
if (token == null) {
return null;
} else if (!_tokenMatches(token, TokenType.PERIOD)) {
return token;
}
token = token.next;
Token nextToken = skipSimpleIdentifier(token);
if (nextToken != null) {
return nextToken;
} else if (_tokenMatches(token, TokenType.CLOSE_PAREN) ||
_tokenMatches(token, TokenType.COMMA)) {
// If the `id.` is followed by something that cannot produce a valid
// structure then assume this is a prefixed identifier but missing the
// trailing identifier
return token;
}
return null;
}
/// Parse a simple identifier, starting at the [startToken], without actually
/// creating a simple identifier or changing the current token. Return the
/// token following the simple identifier that was parsed, or `null` if the
/// given token is not the first token in a valid simple identifier.
///
/// This method must be kept in sync with [parseSimpleIdentifier].
///
/// identifier ::=
/// IDENTIFIER
Token skipSimpleIdentifier(Token startToken) {
if (_tokenMatches(startToken, TokenType.IDENTIFIER) ||
_tokenMatchesPseudoKeyword(startToken)) {
return startToken.next;
}
return null;
}
/// Parse a string literal, starting at the [startToken], without actually
/// creating a string literal or changing the current token. Return the token
/// following the string literal that was parsed, or `null` if the given token
/// is not the first token in a valid string literal.
///
/// This method must be kept in sync with [parseStringLiteral].
///
/// stringLiteral ::=
/// MULTI_LINE_STRING+
/// | SINGLE_LINE_STRING+
Token skipStringLiteral(Token startToken) {
Token token = startToken;
while (token != null && _tokenMatches(token, TokenType.STRING)) {
token = token.next;
TokenType type = token.type;
if (type == TokenType.STRING_INTERPOLATION_EXPRESSION ||
type == TokenType.STRING_INTERPOLATION_IDENTIFIER) {
token = _skipStringInterpolation(token);
}
}
if (identical(token, startToken)) {
return null;
}
return token;
}
/// Parse a type annotation, starting at the [startToken], without actually
/// creating a type annotation or changing the current token. Return the token
/// following the type annotation that was parsed, or `null` if the given
/// token is not the first token in a valid type annotation.
///
/// This method must be kept in sync with [parseTypeAnnotation].
Token skipTypeAnnotation(Token startToken) {
Token next = null;
if (_atGenericFunctionTypeAfterReturnType(startToken)) {
// Generic function type with no return type.
next = skipGenericFunctionTypeAfterReturnType(startToken);
} else {
next = skipTypeWithoutFunction(startToken);
}
while (next != null && _atGenericFunctionTypeAfterReturnType(next)) {
next = skipGenericFunctionTypeAfterReturnType(next);
}
return next;
}
/// Parse a list of type arguments, starting at the [startToken], without
/// actually creating a type argument list or changing the current token.
/// Return the token following the type argument list that was parsed, or
/// `null` if the given token is not the first token in a valid type argument
/// list.
///
/// This method must be kept in sync with [parseTypeArgumentList].
///
/// typeArguments ::=
/// '<' typeList '>'
///
/// typeList ::=
/// type (',' type)*
Token skipTypeArgumentList(Token startToken) {
Token token = startToken;
if (!_tokenMatches(token, TokenType.LT)) {
return null;
}
token = skipTypeAnnotation(token.next);
if (token == null) {
// If the start token '<' is followed by '>'
// then assume this should be type argument list but is missing a type
token = startToken.next;
if (_tokenMatches(token, TokenType.GT)) {
return token.next;
}
return null;
}
while (_tokenMatches(token, TokenType.COMMA)) {
token = skipTypeAnnotation(token.next);
if (token == null) {
return null;
}
}
if (token.type == TokenType.GT) {
return token.next;
} else if (token.type == TokenType.GT_GT) {
Token second = new Token(TokenType.GT, token.offset + 1);
second.setNextWithoutSettingPrevious(token.next);
return second;
}
return null;
}
/// Parse a type name, starting at the [startToken], without actually creating
/// a type name or changing the current token. Return the token following the
/// type name that was parsed, or `null` if the given token is not the first
/// token in a valid type name.
///
/// This method must be kept in sync with [parseTypeName].
///
/// type ::=
/// qualified typeArguments?
Token skipTypeName(Token startToken) {
Token token = skipPrefixedIdentifier(startToken);
if (token == null) {
return null;
}
if (_tokenMatches(token, TokenType.LT)) {
token = skipTypeArgumentList(token);
}
return token;
}
/// Parse a type parameter list, starting at the [startToken], without
/// actually creating a type parameter list or changing the current token.
/// Return the token following the type parameter list that was parsed, or
/// `null` if the given token is not the first token in a valid type parameter
/// list.
///
/// This method must be kept in sync with [parseTypeParameterList].
Token skipTypeParameterList(Token startToken) {
if (!_tokenMatches(startToken, TokenType.LT)) {
return null;
}
int depth = 1;
Token previous = startToken;
Token next = startToken.next;
while (next != previous) {
if (_tokenMatches(next, TokenType.LT)) {
depth++;
} else if (_tokenMatches(next, TokenType.GT)) {
depth--;
if (depth == 0) {
return next.next;
}
}
previous = next;
next = next.next;
}
return null;
}
/// Parse a typeWithoutFunction, starting at the [startToken], without
/// actually creating a TypeAnnotation or changing the current token. Return
/// the token following the typeWithoutFunction that was parsed, or `null` if
/// the given token is not the first token in a valid typeWithoutFunction.
///
/// This method must be kept in sync with [parseTypeWithoutFunction].
Token skipTypeWithoutFunction(Token startToken) {
if (startToken.keyword == Keyword.VOID) {
return startToken.next;
} else {
return skipTypeName(startToken);
}
}
/// Advance to the next token in the token stream.
void _advance() {
_currentToken = _currentToken.next;
}
/// Append the character equivalent of the given [codePoint] to the given
/// [builder]. Use the [startIndex] and [endIndex] to report an error, and
/// don't append anything to the builder, if the code point is invalid. The
/// [escapeSequence] is the escape sequence that was parsed to produce the
/// code point (used for error reporting).
void _appendCodePoint(StringBuffer buffer, String source, int codePoint,
int startIndex, int endIndex) {
if (codePoint < 0 || codePoint > Character.MAX_CODE_POINT) {
String escapeSequence = source.substring(startIndex, endIndex + 1);
_reportErrorForCurrentToken(
ParserErrorCode.INVALID_CODE_POINT, [escapeSequence]);
return;
}
if (codePoint < Character.MAX_VALUE) {
buffer.writeCharCode(codePoint);
} else {
buffer.write(Character.toChars(codePoint));
}
}
/// Return `true` if we are positioned at the keyword 'Function' in a generic
/// function type alias.
bool _atGenericFunctionTypeAfterReturnType(Token startToken) {
if (_tokenMatchesKeyword(startToken, Keyword.FUNCTION)) {
Token next = startToken.next;
if (next != null &&
(_tokenMatches(next, TokenType.OPEN_PAREN) ||
_tokenMatches(next, TokenType.LT))) {
return true;
}
}
return false;
}
/// Convert the given [method] declaration into the nearest valid top-level
/// function declaration (that is, the function declaration that most closely
/// captures the components of the given method declaration).
FunctionDeclaration _convertToFunctionDeclaration(MethodDeclaration method) =>
astFactory.functionDeclaration(
method.documentationComment,
method.metadata,
method.externalKeyword,
method.returnType,
method.propertyKeyword,
method.name,
astFactory.functionExpression(
method.typeParameters, method.parameters, method.body));
/// Return `true` if the current token could be the start of a compilation
/// unit member. This method is used for recovery purposes to decide when to
/// stop skipping tokens after finding an error while parsing a compilation
/// unit member.
bool _couldBeStartOfCompilationUnitMember() {
Keyword keyword = _currentToken.keyword;
Token next = _currentToken.next;
TokenType nextType = next.type;
if ((keyword == Keyword.IMPORT ||
keyword == Keyword.EXPORT ||
keyword == Keyword.LIBRARY ||
keyword == Keyword.PART) &&
nextType != TokenType.PERIOD &&
nextType != TokenType.LT) {
// This looks like the start of a directive
return true;
} else if (keyword == Keyword.CLASS) {
// This looks like the start of a class definition
return true;
} else if (keyword == Keyword.TYPEDEF &&
nextType != TokenType.PERIOD &&
nextType != TokenType.LT) {
// This looks like the start of a typedef
return true;
} else if (keyword == Keyword.VOID ||
((keyword == Keyword.GET || keyword == Keyword.SET) &&
_tokenMatchesIdentifier(next)) ||
(keyword == Keyword.OPERATOR && _isOperator(next))) {
// This looks like the start of a function
return true;
} else if (_matchesIdentifier()) {
if (nextType == TokenType.OPEN_PAREN) {
// This looks like the start of a function
return true;
}
Token token = skipTypeAnnotation(_currentToken);
if (token == null) {
return false;
}
// TODO(brianwilkerson) This looks wrong; should we be checking 'token'?
if (keyword == Keyword.GET ||
keyword == Keyword.SET ||
(keyword == Keyword.OPERATOR && _isOperator(next)) ||
_matchesIdentifier()) {
return true;
}
}
return false;
}
/// Return a synthetic token representing the given [keyword].
Token _createSyntheticKeyword(Keyword keyword) =>
_injectToken(new SyntheticKeywordToken(keyword, _currentToken.offset));
/// Return a synthetic token with the given [type].
Token _createSyntheticToken(TokenType type) =>
_injectToken(new StringToken(type, "", _currentToken.offset));
/// Create and return a new token with the given [type]. The token will
/// replace the first portion of the given [token], so it will have the same
/// offset and will have any comments that might have preceeded the token.
Token _createToken(Token token, TokenType type, {bool isBegin: false}) {
CommentToken comments = token.precedingComments;
if (comments == null) {
if (isBegin) {
return new BeginToken(type, token.offset);
}
return new Token(type, token.offset);
} else if (isBegin) {
return new BeginToken(type, token.offset, comments);
}
return new Token(type, token.offset, comments);
}
/// Check that the given [expression] is assignable and report an error if it
/// isn't.
///
/// assignableExpression ::=
/// primary (arguments* assignableSelector)+
/// | 'super' unconditionalAssignableSelector
/// | identifier
///
/// unconditionalAssignableSelector ::=
/// '[' expression ']'
/// | '.' identifier
///
/// assignableSelector ::=
/// unconditionalAssignableSelector
/// | '?.' identifier
void _ensureAssignable(Expression expression) {
if (expression != null && !expression.isAssignable) {
_reportErrorForCurrentToken(
ParserErrorCode.ILLEGAL_ASSIGNMENT_TO_NON_ASSIGNABLE);
}
}
/// If the current token has the expected type, return it after advancing to
/// the next token. Otherwise report an error and return the current token
/// without advancing.
///
/// Note that the method [_expectGt] should be used if the argument to this
/// method would be [TokenType.GT].
///
/// The [type] is the type of token that is expected.
Token _expect(TokenType type) {
if (_matches(type)) {
return getAndAdvance();
}
// Remove uses of this method in favor of matches?
// Pass in the error code to use to report the error?
if (type == TokenType.SEMICOLON) {
if (_tokenMatches(_currentToken.next, TokenType.SEMICOLON)) {
_reportErrorForCurrentToken(
ParserErrorCode.UNEXPECTED_TOKEN, [_currentToken.lexeme]);
_advance();
return getAndAdvance();
}
_reportErrorForToken(ParserErrorCode.EXPECTED_TOKEN,
_currentToken.previous, [type.lexeme]);
return _createSyntheticToken(TokenType.SEMICOLON);
}
_reportErrorForCurrentToken(ParserErrorCode.EXPECTED_TOKEN, [type.lexeme]);
return _createSyntheticToken(type);
}
/// If the current token has the type [TokenType.GT], return it after
/// advancing to the next token. Otherwise report an error and create a
/// synthetic token.
Token _expectGt() {
if (_matchesGt()) {
return getAndAdvance();
}
_reportErrorForCurrentToken(
ParserErrorCode.EXPECTED_TOKEN, [TokenType.GT.lexeme]);
return _createSyntheticToken(TokenType.GT);
}
/// If the current token is a keyword matching the given [keyword], return it
/// after advancing to the next token. Otherwise report an error and return
/// the current token without advancing.
Token _expectKeyword(Keyword keyword) {
if (_matchesKeyword(keyword)) {
return getAndAdvance();
}
// Remove uses of this method in favor of matches?
// Pass in the error code to use to report the error?
_reportErrorForCurrentToken(
ParserErrorCode.EXPECTED_TOKEN, [keyword.lexeme]);
return _currentToken;
}
/// Search the given list of [ranges] for a range that contains the given
/// [index]. Return the range that was found, or `null` if none of the ranges
/// contain the index.
List<int> _findRange(List<List<int>> ranges, int index) {
int rangeCount = ranges.length;
for (int i = 0; i < rangeCount; i++) {
List<int> range = ranges[i];
if (range[0] <= index && index <= range[1]) {
return range;
} else if (index < range[0]) {
return null;
}
}
return null;
}
/// Return a list of the ranges of characters in the given [comment] that
/// should be treated as code blocks.
List<List<int>> _getCodeBlockRanges(String comment) {
List<List<int>> ranges = <List<int>>[];
int length = comment.length;
if (length < 3) {
return ranges;
}
int index = 0;
int firstChar = comment.codeUnitAt(0);
if (firstChar == 0x2F) {
int secondChar = comment.codeUnitAt(1);
int thirdChar = comment.codeUnitAt(2);
if ((secondChar == 0x2A && thirdChar == 0x2A) ||
(secondChar == 0x2F && thirdChar == 0x2F)) {
index = 3;
}
}
if (StringUtilities.startsWith4(comment, index, 0x20, 0x20, 0x20, 0x20)) {
int end = index + 4;
while (end < length &&
comment.codeUnitAt(end) != 0xD &&
comment.codeUnitAt(end) != 0xA) {
end = end + 1;
}
ranges.add(<int>[index, end]);
index = end;
}
while (index < length) {
int currentChar = comment.codeUnitAt(index);
if (currentChar == 0xD || currentChar == 0xA) {
index = index + 1;
while (index < length &&
Character.isWhitespace(comment.codeUnitAt(index))) {
index = index + 1;
}
if (StringUtilities.startsWith6(
comment, index, 0x2A, 0x20, 0x20, 0x20, 0x20, 0x20)) {
int end = index + 6;
while (end < length &&
comment.codeUnitAt(end) != 0xD &&
comment.codeUnitAt(end) != 0xA) {
end = end + 1;
}
ranges.add(<int>[index, end]);
index = end;
}
} else if (index + 1 < length &&
currentChar == 0x5B &&
comment.codeUnitAt(index + 1) == 0x3A) {
int end = StringUtilities.indexOf2(comment, index + 2, 0x3A, 0x5D);
if (end < 0) {
end = length;
}
ranges.add(<int>[index, end]);
index = end + 1;
} else {
index = index + 1;
}
}
return ranges;
}
/// Return the end token associated with the given [beginToken], or `null` if
/// either the given token is not a begin token or it does not have an end
/// token associated with it.
Token _getEndToken(Token beginToken) {
if (beginToken is BeginToken) {
return beginToken.endToken;
}
return null;
}
/// Inject the given [token] into the token stream immediately before the
/// current token.
Token _injectToken(Token token) {
Token previous = _currentToken.previous;
token.setNext(_currentToken);
previous.setNext(token);
return token;
}
/// Return `true` if the given [character] is a valid hexadecimal digit.
bool _isHexDigit(int character) =>
(0x30 <= character && character <= 0x39) ||
(0x41 <= character && character <= 0x46) ||
(0x61 <= character && character <= 0x66);
bool _isLikelyArgumentList() {
// Try to reduce the amount of lookahead required here before enabling
// generic methods.
if (_matches(TokenType.OPEN_PAREN)) {
return true;
}
Token token = skipTypeArgumentList(_currentToken);
return token != null && _tokenMatches(token, TokenType.OPEN_PAREN);
}
/// Return `true` if it looks like we have found the invocation of a named
/// constructor following the name of the type:
/// ```
/// typeArguments? '.' identifier '('
/// ```
bool _isLikelyNamedInstanceCreation() {
Token token = skipTypeArgumentList(_currentToken);
if (token != null && _tokenMatches(token, TokenType.PERIOD)) {
token = skipSimpleIdentifier(token.next);
if (token != null && _tokenMatches(token, TokenType.OPEN_PAREN)) {
return true;
}
}
return false;
}
/// 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 nextChar = comment.codeUnitAt(index);
if (nextChar == 0x28 || nextChar == 0x3A) {
return true;
}
while (Character.isWhitespace(nextChar)) {
index = index + 1;
if (index >= length) {
return false;
}
nextChar = comment.codeUnitAt(index);
}
return nextChar == 0x5B;
}
/// Return `true` if the given [startToken] appears to be the beginning of an
/// operator declaration.
bool _isOperator(Token startToken) {
// Accept any operator here, even if it is not user definable.
if (!startToken.isOperator) {
return false;
}
// Token "=" means that it is actually a field initializer.
if (startToken.type == TokenType.EQ) {
return false;
}
// Consume all operator tokens.
Token token = startToken.next;
while (token.isOperator) {
token = token.next;
}
// Formal parameter list is expect now.
return _tokenMatches(token, TokenType.OPEN_PAREN);
}
bool _isPeekGenericTypeParametersAndOpenParen() {
Token token = _skipTypeParameterList(_peek());
return token != null && _tokenMatches(token, TokenType.OPEN_PAREN);
}
/// Return `true` if the [startToken] appears to be the first token of a type
/// name that is followed by a variable or field formal parameter.
bool _isTypedIdentifier(Token startToken) {
Token token = skipTypeAnnotation(startToken);
if (token == null) {
return false;
} else if (_tokenMatchesIdentifier(token)) {
return true;
} else if (_tokenMatchesKeyword(token, Keyword.THIS) &&
_tokenMatches(token.next, TokenType.PERIOD) &&
_tokenMatchesIdentifier(token.next.next)) {
return true;
} else if (startToken.next != token &&
!_tokenMatches(token, TokenType.OPEN_PAREN)) {
// The type is more than a simple identifier, so it should be assumed to
// be a type name.
return true;
}
return false;
}
/// Return `true` if the given [expression] is a primary expression that is
/// allowed to be an assignable expression without any assignable selector.
bool _isValidAssignableExpression(Expression expression) {
if (expression is SimpleIdentifier) {
return true;
} else if (expression is PropertyAccess) {
return expression.target is SuperExpression;
} else if (expression is IndexExpression) {
return expression.target is SuperExpression;
}
return false;
}
/// Increments the error reporting lock level. If level is more than `0`, then
/// [reportError] wont report any error.
void _lockErrorListener() {
_errorListenerLock++;
}
/// Return `true` if the current token has the given [type]. Note that the
/// method [_matchesGt] should be used if the argument to this method would be
/// [TokenType.GT].
bool _matches(TokenType type) => _currentToken.type == type;
/// Return `true` if the current token has a type of [TokenType.GT]. Note that
/// this method, unlike other variants, will modify the token stream if
/// possible to match desired type. In particular, if the next token is either
/// a '>>' or '>>>', the token stream will be re-written and `true` will be
/// returned.
bool _matchesGt() {
TokenType currentType = _currentToken.type;
if (currentType == TokenType.GT) {
return true;
} else if (currentType == TokenType.GT_GT) {
Token first = _createToken(_currentToken, TokenType.GT);
Token second = new Token(TokenType.GT, _currentToken.offset + 1);
second.setNext(_currentToken.next);
first.setNext(second);
_currentToken.previous.setNext(first);
_currentToken = first;
return true;
} else if (currentType == TokenType.GT_EQ) {
Token first = _createToken(_currentToken, TokenType.GT);
Token second = new Token(TokenType.EQ, _currentToken.offset + 1);
second.setNext(_currentToken.next);
first.setNext(second);
_currentToken.previous.setNext(first);
_currentToken = first;
return true;
} else if (currentType == TokenType.GT_GT_EQ) {
int offset = _currentToken.offset;
Token first = _createToken(_currentToken, TokenType.GT);
Token second = new Token(TokenType.GT, offset + 1);
Token third = new Token(TokenType.EQ, offset + 2);
third.setNext(_currentToken.next);
second.setNext(third);
first.setNext(second);
_currentToken.previous.setNext(first);
_currentToken = first;
return true;
}
return false;
}
/// Return `true` if the current token is a valid identifier. Valid
/// identifiers include built-in identifiers (pseudo-keywords).
bool _matchesIdentifier() => _tokenMatchesIdentifier(_currentToken);
/// Return `true` if the current token matches the given [keyword].
bool _matchesKeyword(Keyword keyword) =>
_tokenMatchesKeyword(_currentToken, keyword);
/// If the current token has the given [type], then advance to the next token
/// and return `true`. Otherwise, return `false` without advancing. This
/// method should not be invoked with an argument value of [TokenType.GT].
bool _optional(TokenType type) {
if (_currentToken.type == type) {
_advance();
return true;
}
return false;
}
/// Parse an argument list when we need to check for an open paren and recover
/// when there isn't one. Return the argument list that was parsed.
ArgumentList _parseArgumentListChecked() {
if (_matches(TokenType.OPEN_PAREN)) {
return parseArgumentList();
}
_reportErrorForCurrentToken(
ParserErrorCode.EXPECTED_TOKEN, [TokenType.OPEN_PAREN.lexeme]);
// Recovery: Look to see whether there is a close paren that isn't matched
// to an open paren and if so parse the list of arguments as normal.
return astFactory.argumentList(_createSyntheticToken(TokenType.OPEN_PAREN),
null, _createSyntheticToken(TokenType.CLOSE_PAREN));
}
/// Parse an assert within a constructor's initializer list. Return the
/// assert.
///
/// This method assumes that the current token matches `Keyword.ASSERT`.
///
/// assertInitializer ::=
/// 'assert' '(' expression [',' expression] ')'
AssertInitializer _parseAssertInitializer() {
Token keyword = getAndAdvance();
Token leftParen = _expect(TokenType.OPEN_PAREN);
Expression expression = parseExpression2();
Token comma;
Expression message;
if (_matches(TokenType.COMMA)) {
comma = getAndAdvance();
if (_matches(TokenType.CLOSE_PAREN)) {
comma = null;
} else {
message = parseExpression2();
if (_matches(TokenType.COMMA)) {
getAndAdvance();
}
}
}
Token rightParen = _expect(TokenType.CLOSE_PAREN);
return astFactory.assertInitializer(
keyword, leftParen, expression, comma, message, rightParen);
}
/// Parse a block when we need to check for an open curly brace and recover
/// when there isn't one. Return the block that was parsed.
///
/// block ::=
/// '{' statements '}'
Block _parseBlockChecked() {
if (_matches(TokenType.OPEN_CURLY_BRACKET)) {
return parseBlock();
}
// TODO(brianwilkerson) Improve the error message.
_reportErrorForCurrentToken(
ParserErrorCode.EXPECTED_TOKEN, [TokenType.OPEN_CURLY_BRACKET.lexeme]);
// Recovery: Check for an unmatched closing curly bracket and parse
// statements until it is reached.
return astFactory.block(_createSyntheticToken(TokenType.OPEN_CURLY_BRACKET),
null, _createSyntheticToken(TokenType.CLOSE_CURLY_BRACKET));
}
/// Parse a list of class members. The [className] is the name of the class
/// whose members are being parsed. The [closingBracket] is the closing
/// bracket for the class, or `null` if the closing bracket is missing.
/// Return the list of class members that were parsed.
///
/// classMembers ::=
/// (metadata memberDefinition)*
List<ClassMember> _parseClassMembers(String className, Token closingBracket) {
List<ClassMember> members = <ClassMember>[];
Token memberStart = _currentToken;
TokenType type = _currentToken.type;
Keyword keyword = _currentToken.keyword;
while (type != TokenType.EOF &&
type != TokenType.CLOSE_CURLY_BRACKET &&
(closingBracket != null ||
(keyword != Keyword.CLASS && keyword != Keyword.TYPEDEF))) {
if (type == TokenType.SEMICOLON) {
_reportErrorForToken(ParserErrorCode.UNEXPECTED_TOKEN, _currentToken,
[_currentToken.lexeme]);
_advance();
} else {
ClassMember member = parseClassMember(className);
if (member != null) {
members.add(member);
}
}
if (identical(_currentToken, memberStart)) {
_reportErrorForToken(ParserErrorCode.UNEXPECTED_TOKEN, _currentToken,
[_currentToken.lexeme]);
_advance();
}
memberStart = _currentToken;
type = _currentToken.type;
keyword = _currentToken.keyword;
}
return members;
}
/// Parse a class type alias. The [commentAndMetadata] is the metadata to be
/// associated with the member. The [abstractKeyword] is the token
/// representing the 'abstract' keyword. The [classKeyword] is the token
/// representing the 'class' keyword. The [className] is the name of the
/// alias, and the [typeParameters] are the type parameters following the
/// name. Return the class type alias that was parsed.
///
/// classTypeAlias ::=
/// identifier typeParameters? '=' 'abstract'? mixinApplication
///
/// mixinApplication ::=
/// type withClause implementsClause? ';'
ClassTypeAlias _parseClassTypeAliasAfterName(
CommentAndMetadata commentAndMetadata,
Token abstractKeyword,
Token classKeyword,
SimpleIdentifier className,
TypeParameterList typeParameters) {
Token equals = _expect(TokenType.EQ);
TypeName superclass = parseTypeName(false);
WithClause withClause = null;
if (_matchesKeyword(Keyword.WITH)) {
withClause = parseWithClause();
} else {
_reportErrorForCurrentToken(
ParserErrorCode.EXPECTED_TOKEN, [Keyword.WITH.lexeme]);
}
ImplementsClause implementsClause = null;
if (_matchesKeyword(Keyword.IMPLEMENTS)) {
implementsClause = parseImplementsClause();
}
Token semicolon;
if (_matches(TokenType.SEMICOLON)) {
semicolon = getAndAdvance();
} else {
if (_matches(TokenType.OPEN_CURLY_BRACKET)) {
_reportErrorForCurrentToken(
ParserErrorCode.EXPECTED_TOKEN, [TokenType.SEMICOLON.lexeme]);
Token leftBracket = getAndAdvance();
_parseClassMembers(className.name, _getEndToken(leftBracket));
_expect(TokenType.CLOSE_CURLY_BRACKET);
} else {
_reportErrorForToken(ParserErrorCode.EXPECTED_TOKEN,
_currentToken.previous, [TokenType.SEMICOLON.lexeme]);
}
semicolon = _createSyntheticToken(TokenType.SEMICOLON);
}
return astFactory.classTypeAlias(
commentAndMetadata.comment,
commentAndMetadata.metadata,
classKeyword,
className,
typeParameters,
equals,
abstractKeyword,
superclass,
withClause,
implementsClause,
semicolon);
}
/// Parse a list of configurations. Return the configurations that were
/// parsed, or `null` if there are no configurations.
List<Configuration> _parseConfigurations() {
List<Configuration> configurations = null;
while (_matchesKeyword(Keyword.IF)) {
configurations ??= <Configuration>[];
configurations.add(parseConfiguration());
}
return configurations;
}
ConstructorDeclaration _parseConstructor(
CommentAndMetadata commentAndMetadata,
Token externalKeyword,
Token constKeyword,
Token factoryKeyword,
SimpleIdentifier returnType,
Token period,
SimpleIdentifier name,
FormalParameterList parameters) {
bool bodyAllowed = externalKeyword == null;
Token separator = null;
List<ConstructorInitializer> initializers = null;
if (_matches(TokenType.COLON)) {
separator = getAndAdvance();
initializers = <ConstructorInitializer>[];
do {
Keyword keyword = _currentToken.keyword;
if (keyword == Keyword.THIS) {
TokenType nextType = _peek().type;
if (nextType == TokenType.OPEN_PAREN) {
bodyAllowed = false;
initializers.add(parseRedirectingConstructorInvocation(false));
} else if (nextType == TokenType.PERIOD &&
_tokenMatches(_peekAt(3), TokenType.OPEN_PAREN)) {
bodyAllowed = false;
initializers.add(parseRedirectingConstructorInvocation(true));
} else {
initializers.add(parseConstructorFieldInitializer(true));
}
} else if (keyword == Keyword.SUPER) {
initializers.add(parseSuperConstructorInvocation());
} else if (_matches(TokenType.OPEN_CURLY_BRACKET) ||
_matches(TokenType.FUNCTION)) {
_reportErrorForCurrentToken(ParserErrorCode.MISSING_INITIALIZER);
} else if (_matchesKeyword(Keyword.ASSERT)) {
initializers.add(_parseAssertInitializer());
} else {
initializers.add(parseConstructorFieldInitializer(false));
}
} while (_optional(TokenType.COMMA));
if (factoryKeyword != null) {
_reportErrorForToken(
ParserErrorCode.FACTORY_WITH_INITIALIZERS, factoryKeyword);
}
}
ConstructorName redirectedConstructor = null;
FunctionBody body;
if (_matches(TokenType.EQ)) {
separator = getAndAdvance();
redirectedConstructor = parseConstructorName();
body = astFactory.emptyFunctionBody(_expect(TokenType.SEMICOLON));
if (factoryKeyword == null) {
_reportErrorForNode(
ParserErrorCode.REDIRECTION_IN_NON_FACTORY_CONSTRUCTOR,
redirectedConstructor);
}
} else {
body =
parseFunctionBody(true, ParserErrorCode.MISSING_FUNCTION_BODY, false);
if (constKeyword != null &&
factoryKeyword != null &&
externalKeyword == null &&
body is! NativeFunctionBody) {
_reportErrorForToken(ParserErrorCode.CONST_FACTORY, factoryKeyword);
} else if (body is EmptyFunctionBody) {
if (factoryKeyword != null &&
externalKeyword == null &&
_parseFunctionBodies) {
_reportErrorForToken(
ParserErrorCode.FACTORY_WITHOUT_BODY, factoryKeyword);
}
} else {
if (constKeyword != null && body is! NativeFunctionBody) {
_reportErrorForNode(
ParserErrorCode.CONST_CONSTRUCTOR_WITH_BODY, body);
} else if (externalKeyword != null) {
_reportErrorForNode(
ParserErrorCode.EXTERNAL_CONSTRUCTOR_WITH_BODY, body);
} else if (!bodyAllowed) {
_reportErrorForNode(
ParserErrorCode.REDIRECTING_CONSTRUCTOR_WITH_BODY, body);
}
}
}
return astFactory.constructorDeclaration(
commentAndMetadata.comment,
commentAndMetadata.metadata,
externalKeyword,
constKeyword,
factoryKeyword,
returnType,
period,
name,
parameters,
separator,
initializers,
redirectedConstructor,
body);
}
/// Parse an enum constant declaration. Return the enum constant declaration
/// that was parsed.
///
/// Specified:
///
/// enumConstant ::=
/// id
///
/// Actual:
///
/// enumConstant ::=
/// metadata id
EnumConstantDeclaration _parseEnumConstantDeclaration() {
CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
SimpleIdentifier name;
if (_matchesIdentifier()) {
name = _parseSimpleIdentifierUnchecked(isDeclaration: true);
} else {
name = createSyntheticIdentifier();
}
return astFactory.enumConstantDeclaration(
commentAndMetadata.comment, commentAndMetadata.metadata, name);
}
/// Parse a list of formal parameters given that the list starts with the
/// given [leftParenthesis]. Return the formal parameters that were parsed.
FormalParameterList _parseFormalParameterListAfterParen(Token leftParenthesis,
{bool inFunctionType: false}) {
if (_matches(TokenType.CLOSE_PAREN)) {
return astFactory.formalParameterList(
leftParenthesis, null, null, null, getAndAdvance());
}
//
// Even though it is invalid to have default parameters outside of brackets,
// required parameters inside of brackets, or multiple groups of default and
// named parameters, we allow all of these cases so that we can recover
// better.
//
List<FormalParameter> parameters = <FormalParameter>[];
Token leftSquareBracket = null;
Token rightSquareBracket = null;
Token leftCurlyBracket = null;
Token rightCurlyBracket = null;
ParameterKind kind = ParameterKind.REQUIRED;
bool firstParameter = true;
bool reportedMultiplePositionalGroups = false;
bool reportedMultipleNamedGroups = false;
bool reportedMixedGroups = false;
bool wasOptionalParameter = false;
Token initialToken = null;
do {
if (firstParameter) {
firstParameter = false;
} else if (!_optional(TokenType.COMMA)) {
// TODO(brianwilkerson) The token is wrong, we need to recover from this
// case.
if (_getEndToken(leftParenthesis) != null) {
_reportErrorForCurrentToken(
ParserErrorCode.EXPECTED_TOKEN, [TokenType.COMMA.lexeme]);
} else {
_reportErrorForToken(ParserErrorCode.MISSING_CLOSING_PARENTHESIS,
_currentToken.previous);
break;
}
}
initialToken = _currentToken;
//
// Handle the beginning of parameter groups.
//
TokenType type = _currentToken.type;
if (type == TokenType.OPEN_SQUARE_BRACKET) {
wasOptionalParameter = true;
if (leftSquareBracket != null && !reportedMultiplePositionalGroups) {
_reportErrorForCurrentToken(
ParserErrorCode.MULTIPLE_POSITIONAL_PARAMETER_GROUPS);
reportedMultiplePositionalGroups = true;
}
if (leftCurlyBracket != null && !reportedMixedGroups) {
_reportErrorForCurrentToken(ParserErrorCode.MIXED_PARAMETER_GROUPS);
reportedMixedGroups = true;
}
leftSquareBracket = getAndAdvance();
kind = ParameterKind.POSITIONAL;
} else if (type == TokenType.OPEN_CURLY_BRACKET) {
wasOptionalParameter = true;
if (leftCurlyBracket != null && !reportedMultipleNamedGroups) {
_reportErrorForCurrentToken(
ParserErrorCode.MULTIPLE_NAMED_PARAMETER_GROUPS);
reportedMultipleNamedGroups = true;
}
if (leftSquareBracket != null && !reportedMixedGroups) {
_reportErrorForCurrentToken(ParserErrorCode.MIXED_PARAMETER_GROUPS);
reportedMixedGroups = true;
}
leftCurlyBracket = getAndAdvance();
kind = ParameterKind.NAMED;
}
//
// Parse and record the parameter.
//
FormalParameter parameter =
parseFormalParameter(kind, inFunctionType: inFunctionType);
parameters.add(parameter);
if (kind == ParameterKind.REQUIRED && wasOptionalParameter) {
_reportErrorForNode(
ParserErrorCode.NORMAL_BEFORE_OPTIONAL_PARAMETERS, parameter);
}
//
// Handle the end of parameter groups.
//
// TODO(brianwilkerson) Improve the detection and reporting of missing and
// mismatched delimiters.
type = _currentToken.type;
// Advance past trailing commas as appropriate.
if (type == TokenType.COMMA) {
// Only parse commas trailing normal (non-positional/named) params.
if (rightSquareBracket == null && rightCurlyBracket == null) {
Token next = _peek();
if (next.type == TokenType.CLOSE_PAREN ||
next.type == TokenType.CLOSE_CURLY_BRACKET ||
next.type == TokenType.CLOSE_SQUARE_BRACKET) {
_advance();
type = _currentToken.type;
}
}
}
if (type == TokenType.CLOSE_SQUARE_BRACKET) {
rightSquareBracket = getAndAdvance();
if (leftSquareBracket == null) {
if (leftCurlyBracket != null) {
_reportErrorForCurrentToken(
ParserErrorCode.WRONG_TERMINATOR_FOR_PARAMETER_GROUP,
['}', ']']);
rightCurlyBracket = rightSquareBracket;
rightSquareBracket = null;
// Skip over synthetic closer inserted by fasta
// since we've already reported an error
if (_currentToken.type == TokenType.CLOSE_CURLY_BRACKET &&
_currentToken.isSynthetic) {
_advance();
}
} else {
_reportErrorForCurrentToken(
ParserErrorCode.UNEXPECTED_TERMINATOR_FOR_PARAMETER_GROUP,
["["]);
}
}
kind = ParameterKind.REQUIRED;
} else if (type == TokenType.CLOSE_CURLY_BRACKET) {
rightCurlyBracket = getAndAdvance();
if (leftCurlyBracket == null) {
if (leftSquareBracket != null) {
_reportErrorForCurrentToken(
ParserErrorCode.WRONG_TERMINATOR_FOR_PARAMETER_GROUP,
[']', '}']);
rightSquareBracket = rightCurlyBracket;
rightCurlyBracket = null;
// Skip over synthetic closer inserted by fasta
// since we've already reported an error
if (_currentToken.type == TokenType.CLOSE_SQUARE_BRACKET &&
_currentToken.isSynthetic) {
_advance();
}
} else {
_reportErrorForCurrentToken(
ParserErrorCode.UNEXPECTED_TERMINATOR_FOR_PARAMETER_GROUP,
["{"]);
}
}
kind = ParameterKind.REQUIRED;
}
} while (!_matches(TokenType.CLOSE_PAREN) &&
!identical(initialToken, _currentToken));
Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
//
// Check that the groups were closed correctly.
//
if (leftSquareBracket != null && rightSquareBracket == null) {
_reportErrorForCurrentToken(
ParserErrorCode.MISSING_TERMINATOR_FOR_PARAMETER_GROUP, ["]"]);
}
if (leftCurlyBracket != null && rightCurlyBracket == null) {
_reportErrorForCurrentToken(
ParserErrorCode.MISSING_TERMINATOR_FOR_PARAMETER_GROUP, ["}"]);
}
//
// Build the parameter list.
//
leftSquareBracket ??= leftCurlyBracket;
rightSquareBracket ??= rightCurlyBracket;
return astFactory.formalParameterList(leftParenthesis, parameters,
leftSquareBracket, rightSquareBracket, rightParenthesis);
}
/// Parse a list of formal parameters. Return the formal parameters that were
/// parsed.
///
/// This method assumes that the current token matches `TokenType.OPEN_PAREN`.
FormalParameterList _parseFormalParameterListUnchecked(
{bool inFunctionType: false}) {
return _parseFormalParameterListAfterParen(getAndAdvance(),
inFunctionType: inFunctionType);
}
/// Parse a function declaration statement. The [commentAndMetadata] is the
/// documentation comment and metadata to be associated with the declaration.
/// The [returnType] is the return type, or `null` if there is no return type.
/// Return the function declaration statement that was parsed.
///
/// functionDeclarationStatement ::=
/// functionSignature functionBody
Statement _parseFunctionDeclarationStatementAfterReturnType(
CommentAndMetadata commentAndMetadata, TypeAnnotation returnType) {
FunctionDeclaration declaration =
parseFunctionDeclaration(commentAndMetadata, null, returnType);
Token propertyKeyword = declaration.propertyKeyword;
if (propertyKeyword != null) {
if (propertyKeyword.keyword == Keyword.GET) {
_reportErrorForToken(
ParserErrorCode.GETTER_IN_FUNCTION, propertyKeyword);
} else {
_reportErrorForToken(
ParserErrorCode.SETTER_IN_FUNCTION, propertyKeyword);
}
}
return astFactory.functionDeclarationStatement(declaration);
}
/// Parse a function type alias. The [commentAndMetadata] is the metadata to
/// be associated with the member. The [keyword] is the token representing the
/// 'typedef' keyword. Return the function type alias that was parsed.
///
/// functionTypeAlias ::=
/// functionPrefix typeParameterList? formalParameterList ';'
///
/// functionPrefix ::=
/// returnType? name
FunctionTypeAlias _parseFunctionTypeAlias(
CommentAndMetadata commentAndMetadata, Token keyword) {
TypeAnnotation returnType = null;
if (hasReturnTypeInTypeAlias) {
returnType = parseTypeAnnotation(false);
}
SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
TypeParameterList typeParameters = null;
if (_matches(TokenType.LT)) {
typeParameters = parseTypeParameterList();
}
TokenType type = _currentToken.type;
if (type == TokenType.SEMICOLON || type == TokenType.EOF) {
_reportErrorForCurrentToken(ParserErrorCode.MISSING_TYPEDEF_PARAMETERS);
FormalParameterList parameters = astFactory.formalParameterList(
_createSyntheticToken(TokenType.OPEN_PAREN),
null,
null,
null,
_createSyntheticToken(TokenType.CLOSE_PAREN));
Token semicolon = _expect(TokenType.SEMICOLON);
return astFactory.functionTypeAlias(
commentAndMetadata.comment,
commentAndMetadata.metadata,
keyword,
returnType,
name,
typeParameters,
parameters,
semicolon);
} else if (type == TokenType.OPEN_PAREN) {
FormalParameterList parameters = _parseFormalParameterListUnchecked();
_validateFormalParameterList(parameters);
Token semicolon = _expect(TokenType.SEMICOLON);
return astFactory.functionTypeAlias(
commentAndMetadata.comment,
commentAndMetadata.metadata,
keyword,
returnType,
name,
typeParameters,
parameters,
semicolon);
} else {
_reportErrorForCurrentToken(ParserErrorCode.MISSING_TYPEDEF_PARAMETERS);
// Recovery: At the very least we should skip to the start of the next
// valid compilation unit member, allowing for the possibility of finding
// the typedef parameters before that point.
return astFactory.functionTypeAlias(
commentAndMetadata.comment,
commentAndMetadata.metadata,
keyword,
returnType,
name,
typeParameters,
astFactory.formalParameterList(
_createSyntheticToken(TokenType.OPEN_PAREN),
null,
null,
null,
_createSyntheticToken(TokenType.CLOSE_PAREN)),
_createSyntheticToken(TokenType.SEMICOLON));
}
}
/// Parse the generic method or function's type parameters.
///
/// For backwards compatibility this can optionally use comments.
/// See [parseGenericMethodComments].
TypeParameterList _parseGenericMethodTypeParameters() {
if (_matches(TokenType.LT)) {
return parseTypeParameterList();
}
return null;
}
/// Parse a library name. The [missingNameError] is the error code to be used
/// if the library name is missing. The [missingNameToken] is the token
/// associated with the error produced if the library name is missing. Return
/// the library name that was parsed.
///
/// libraryName ::=
/// libraryIdentifier
LibraryIdentifier _parseLibraryName(
ParserErrorCode missingNameError, Token missingNameToken) {
if (_matchesIdentifier()) {
return parseLibraryIdentifier();
} else if (_matches(TokenType.STRING)) {
// Recovery: This should be extended to handle arbitrary tokens until we
// can find a token that can start a compilation unit member.
StringLiteral string = parseStringLiteral();
_reportErrorForNode(ParserErrorCode.NON_IDENTIFIER_LIBRARY_NAME, string);
} else {
_reportErrorForToken(missingNameError, missingNameToken);
}
return astFactory
.libraryIdentifier(<SimpleIdentifier>[createSyntheticIdentifier()]);
}
/// Parse a method declaration. The [commentAndMetadata] is the documentation
/// comment and metadata to be associated with the declaration. The
/// [externalKeyword] is the 'external' token. The [staticKeyword] is the
/// static keyword, or `null` if the getter is not static. The [returnType] is
/// the return type of the method. The [name] is the name of the method. The
/// [parameters] is the parameters to the method. Return the method
/// declaration that was parsed.
///
/// functionDeclaration ::=
/// ('external' 'static'?)? functionSignature functionBody
/// | 'external'? functionSignature ';'
MethodDeclaration _parseMethodDeclarationAfterParameters(
CommentAndMetadata commentAndMetadata,
Token externalKeyword,
Token staticKeyword,
TypeAnnotation returnType,
SimpleIdentifier name,
TypeParameterList typeParameters,
FormalParameterList parameters) {
FunctionBody body = parseFunctionBody(
externalKeyword != null || staticKeyword == null,
ParserErrorCode.MISSING_FUNCTION_BODY,
false);
if (externalKeyword != null) {
if (body is! EmptyFunctionBody) {
_reportErrorForNode(ParserErrorCode.EXTERNAL_METHOD_WITH_BODY, body);
}
} else if (staticKeyword != null) {
if (body is EmptyFunctionBody && _parseFunctionBodies) {
_reportErrorForNode(ParserErrorCode.ABSTRACT_STATIC_METHOD, body);
}
}
return astFactory.methodDeclaration(
commentAndMetadata.comment,
commentAndMetadata.metadata,
externalKeyword,
staticKeyword,
returnType,
null,
null,
name,
typeParameters,
parameters,
body);
}
/// Parse a method declaration. The [commentAndMetadata] is the documentation
/// comment and metadata to be associated with the declaration. The
/// [externalKeyword] is the 'external' token. The [staticKeyword] is the
/// static keyword, or `null` if the getter is not static. The [returnType] is
/// the return type of the method. Return the method declaration that was
/// parsed.
///
/// functionDeclaration ::=
/// 'external'? 'static'? functionSignature functionBody
/// | 'external'? functionSignature ';'
MethodDeclaration _parseMethodDeclarationAfterReturnType(
CommentAndMetadata commentAndMetadata,
Token externalKeyword,
Token staticKeyword,
TypeAnnotation returnType) {
SimpleIdentifier methodName = parseSimpleIdentifier(isDeclaration: true);
TypeParameterList typeParameters = _parseGenericMethodTypeParameters();
FormalParameterList parameters;
TokenType type = _currentToken.type;
// TODO(brianwilkerson) Figure out why we care what the current token is if
// it isn't a paren.
if (type != TokenType.OPEN_PAREN &&
(type == TokenType.OPEN_CURLY_BRACKET || type == TokenType.FUNCTION)) {
_reportErrorForToken(
ParserErrorCode.MISSING_METHOD_PARAMETERS, _currentToken.previous);
parameters = astFactory.formalParameterList(
_createSyntheticToken(TokenType.OPEN_PAREN),
null,
null,
null,
_createSyntheticToken(TokenType.CLOSE_PAREN));
} else {
parameters = parseFormalParameterList();
}
_validateFormalParameterList(parameters);
return _parseMethodDeclarationAfterParameters(
commentAndMetadata,
externalKeyword,
staticKeyword,
returnType,
methodName,
typeParameters,
parameters);
}
/// Parse a class native clause. Return the native clause that was parsed.
///
/// This method assumes that the current token matches `_NATIVE`.
///
/// classNativeClause ::=
/// 'native' name
NativeClause _parseNativeClause() {
Token keyword = getAndAdvance();
StringLiteral name = parseStringLiteral();
return astFactory.nativeClause(keyword, name);
}
/// Parse an operator declaration starting after the 'operator' keyword. The
/// [commentAndMetadata] is the documentation comment and metadata to be
/// associated with the declaration. The [externalKeyword] is the 'external'
/// token. The [returnType] is the return type that has already been parsed,
/// or `null` if there was no return type. The [operatorKeyword] is the
/// 'operator' keyword. Return the operator declaration that was parsed.
///
/// operatorDeclaration ::=
/// operatorSignature (';' | functionBody)
///
/// operatorSignature ::=
/// 'external'? returnType? 'operator' operator formalParameterList
MethodDeclaration _parseOperatorAfterKeyword(
CommentAndMetadata commentAndMetadata,
Token externalKeyword,
TypeAnnotation returnType,
Token operatorKeyword) {
if (!_currentToken.isUserDefinableOperator) {
_reportErrorForCurrentToken(
_currentToken.type == TokenType.EQ_EQ_EQ
? ParserErrorCode.INVALID_OPERATOR
: ParserErrorCode.NON_USER_DEFINABLE_OPERATOR,
[_currentToken.lexeme]);
}
SimpleIdentifier name =
astFactory.simpleIdentifier(getAndAdvance(), isDeclaration: true);
if (_matches(TokenType.EQ)) {
Token previous = _currentToken.previous;
if ((_tokenMatches(previous, TokenType.EQ_EQ) ||
_tokenMatches(previous, TokenType.BANG_EQ)) &&
_currentToken.offset == previous.offset + 2) {
_reportErrorForCurrentToken(ParserErrorCode.INVALID_OPERATOR,
["${previous.lexeme}${_currentToken.lexeme}"]);
_advance();
}
}
FormalParameterList parameters = parseFormalParameterList();
_validateFormalParameterList(parameters);
FunctionBody body =
parseFunctionBody(true, ParserErrorCode.MISSING_FUNCTION_BODY, false);
if (externalKeyword != null && body is! EmptyFunctionBody) {
_reportErrorForCurrentToken(ParserErrorCode.EXTERNAL_OPERATOR_WITH_BODY);
}
return astFactory.methodDeclaration(
commentAndMetadata.comment,
commentAndMetadata.metadata,
externalKeyword,
null,
returnType,
null,
operatorKeyword,
name,
null,
parameters,
body);
}
/// Parse a return type if one is given, otherwise return `null` without
/// advancing. Return the return type that was parsed.
TypeAnnotation _parseOptionalReturnType() {
Keyword keyword = _currentToken.keyword;
if (keyword == Keyword.VOID) {
if (_atGenericFunctionTypeAfterReturnType(_peek())) {
return parseTypeAnnotation(false);
}
return astFactory.typeName(
astFactory.simpleIdentifier(getAndAdvance()), null);
} else if (_matchesIdentifier()) {
Token next = _peek();
if (keyword != Keyword.GET &&
keyword != Keyword.SET &&
keyword != Keyword.OPERATOR &&
(_tokenMatchesIdentifier(next) ||
_tokenMatches(next, TokenType.LT))) {
Token afterTypeParameters = _skipTypeParameterList(next);
if (afterTypeParameters != null &&
_tokenMatches(afterTypeParameters, TokenType.OPEN_PAREN)) {
// If the identifier is followed by type parameters and a parenthesis,
// then the identifier is the name of a generic method, not a return
// type.
return null;
}
return parseTypeAnnotation(false);
}
Token next2 = next.next;
Token next3 = next2.next;
if (_tokenMatches(next, TokenType.PERIOD) &&
_tokenMatchesIdentifier(next2) &&
(_tokenMatchesIdentifier(next3) ||
_tokenMatches(next3, TokenType.LT))) {
return parseTypeAnnotation(false);
}
}
return null;
}
/// Parse a [TypeArgumentList] if present, otherwise return null.
/// This also supports the comment form, if enabled: `/*<T>*/`
TypeArgumentList _parseOptionalTypeArguments() {
if (_matches(TokenType.LT)) {
return parseTypeArgumentList();
}
return null;
}
/// Parse a part directive. The [commentAndMetadata] is the metadata to be
/// associated with the directive. Return the part or part-of directive that
/// was parsed.
///
/// This method assumes that the current token matches `Keyword.PART`.
///
/// partDirective ::=
/// metadata 'part' stringLiteral ';'
Directive _parsePartDirective(CommentAndMetadata commentAndMetadata) {
Token partKeyword = getAndAdvance();
StringLiteral partUri = _parseUri();
Token semicolon = _expect(TokenType.SEMICOLON);
return astFactory.partDirective(commentAndMetadata.comment,
commentAndMetadata.metadata, partKeyword, partUri, semicolon);
}
/// Parse a part-of directive. The [commentAndMetadata] is the metadata to be
/// associated with the directive. Return the part or part-of directive that
/// was parsed.
///
/// This method assumes that the current token matches [Keyword.PART] and that
/// the following token matches the identifier 'of'.
///
/// partOfDirective ::=
/// metadata 'part' 'of' identifier ';'
Directive _parsePartOfDirective(CommentAndMetadata commentAndMetadata) {
Token partKeyword = getAndAdvance();
Token ofKeyword = getAndAdvance();
if (_matches(TokenType.STRING)) {
StringLiteral libraryUri = _parseUri();
Token semicolon = _expect(TokenType.SEMICOLON);
return astFactory.partOfDirective(
commentAndMetadata.comment,
commentAndMetadata.metadata,
partKeyword,
ofKeyword,
libraryUri,
null,
semicolon);
}
LibraryIdentifier libraryName = _parseLibraryName(
ParserErrorCode.MISSING_NAME_IN_PART_OF_DIRECTIVE, ofKeyword);
Token semicolon = _expect(TokenType.SEMICOLON);
return astFactory.partOfDirective(
commentAndMetadata.comment,
commentAndMetadata.metadata,
partKeyword,
ofKeyword,
null,
libraryName,
semicolon);
}
/// Parse a prefixed identifier given that the given [qualifier] was already
/// parsed. Return the prefixed identifier that was parsed.
///
/// prefixedIdentifier ::=
/// identifier ('.' identifier)?
Identifier _parsePrefixedIdentifierAfterIdentifier(
SimpleIdentifier qualifier) {
if (!_matches(TokenType.PERIOD)) {
return qualifier;
}
Token period = getAndAdvance();
SimpleIdentifier qualified = parseSimpleIdentifier();
return astFactory.prefixedIdentifier(qualifier, period, qualified);
}
/// Parse a prefixed identifier. Return the prefixed identifier that was
/// parsed.
///
/// This method assumes that the current token matches an identifier.
///
/// prefixedIdentifier ::=
/// identifier ('.' identifier)?
Identifier _parsePrefixedIdentifierUnchecked() {
return _parsePrefixedIdentifierAfterIdentifier(
_parseSimpleIdentifierUnchecked());
}
/// Parse a simple identifier. Return the simple identifier that was parsed.
///
/// This method assumes that the current token matches an identifier.
///
/// identifier ::=
/// IDENTIFIER
SimpleIdentifier _parseSimpleIdentifierUnchecked(
{bool isDeclaration: false}) {
String lexeme = _currentToken.lexeme;
if ((_inAsync || _inGenerator) &&
(lexeme == ASYNC || lexeme == _AWAIT || lexeme == _YIELD)) {
_reportErrorForCurrentToken(
ParserErrorCode.ASYNC_KEYWORD_USED_AS_IDENTIFIER);
}
return astFactory.simpleIdentifier(getAndAdvance(),
isDeclaration: isDeclaration);
}
/// Parse a list of statements within a switch statement. Return the
/// statements that were parsed.
///
/// statements ::=
/// statement*
List<Statement> _parseStatementList() {
List<Statement> statements = <Statement>[];
Token statementStart = _currentToken;
TokenType type = _currentToken.type;
while (type != TokenType.EOF &&
type != TokenType.CLOSE_CURLY_BRACKET &&
!isSwitchMember()) {
statements.add(parseStatement2());
if (identical(_currentToken, statementStart)) {
_reportErrorForToken(ParserErrorCode.UNEXPECTED_TOKEN, _currentToken,
[_currentToken.lexeme]);
_advance();
}
statementStart = _currentToken;
type = _currentToken.type;
}
return statements;
}
/// Parse a string literal that contains interpolations. Return the string
/// literal that was parsed.
///
/// This method assumes that the current token matches either
/// [TokenType.STRING_INTERPOLATION_EXPRESSION] or
/// [TokenType.STRING_INTERPOLATION_IDENTIFIER].
StringInterpolation _parseStringInterpolation(Token string) {
List<InterpolationElement> elements = <InterpolationElement>[
astFactory.interpolationString(
string, computeStringValue(string.lexeme, true, false))
];
bool hasMore = true;
bool isExpression = _matches(TokenType.STRING_INTERPOLATION_EXPRESSION);
while (hasMore) {
if (isExpression) {
Token openToken = getAndAdvance();
bool wasInInitializer = _inInitializer;
_inInitializer = false;
try {
Expression expression = parseExpression2();
Token rightBracket = _expect(TokenType.CLOSE_CURLY_BRACKET);
elements.add(astFactory.interpolationExpression(
openToken, expression, rightBracket));
} finally {
_inInitializer = wasInInitializer;
}
} else {
Token openToken = getAndAdvance();
Expression expression = null;
if (_matchesKeyword(Keyword.THIS)) {
expression = astFactory.thisExpression(getAndAdvance());
} else {
expression = parseSimpleIdentifier();
}
elements.add(
astFactory.interpolationExpression(openToken, expression, null));
}
if (_matches(TokenType.STRING)) {
string = getAndAdvance();
isExpression = _matches(TokenType.STRING_INTERPOLATION_EXPRESSION);
hasMore =
isExpression || _matches(TokenType.STRING_INTERPOLATION_IDENTIFIER);
elements.add(astFactory.interpolationString(
string, computeStringValue(string.lexeme, false, !hasMore)));
} else {
hasMore = false;
}
}
return astFactory.stringInterpolation(elements);
}
/// Parse a string literal. Return the string literal that was parsed.
///
/// This method assumes that the current token matches `TokenType.STRING`.
///
/// stringLiteral ::=
/// MULTI_LINE_STRING+
/// | SINGLE_LINE_STRING+
StringLiteral _parseStringLiteralUnchecked() {
List<StringLiteral> strings = <StringLiteral>[];
do {
Token string = getAndAdvance();
if (_matches(TokenType.STRING_INTERPOLATION_EXPRESSION) ||
_matches(TokenType.STRING_INTERPOLATION_IDENTIFIER)) {
strings.add(_parseStringInterpolation(string));
} else {
strings.add(astFactory.simpleStringLiteral(
string, computeStringValue(string.lexeme, true, true)));
}
} while (_matches(TokenType.STRING));
return strings.length == 1
? strings[0]
: astFactory.adjacentStrings(strings);
}
/// Parse a type annotation, possibly superseded by a type name in a comment.
/// Return the type name that was parsed.
///
/// This method assumes that the current token is an identifier.
///
/// type ::=
/// qualified typeArguments?
TypeAnnotation _parseTypeAnnotationAfterIdentifier() {
return parseTypeAnnotation(false);
}
TypeName _parseTypeName(bool inExpression) {
Identifier typeName;
if (_matchesIdentifier()) {
typeName = _parsePrefixedIdentifierUnchecked();
} else if (_matchesKeyword(Keyword.VAR)) {
_reportErrorForCurrentToken(ParserErrorCode.VAR_AS_TYPE_NAME);
typeName = astFactory.simpleIdentifier(getAndAdvance());
} else {
typeName = createSyntheticIdentifier();
_reportErrorForCurrentToken(ParserErrorCode.EXPECTED_TYPE_NAME);
}
TypeArgumentList typeArguments = _parseOptionalTypeArguments();
return astFactory.typeName(typeName, typeArguments);
}
/// Parse a string literal representing a URI. Return the string literal that
/// was parsed.
StringLiteral _parseUri() {
// TODO(brianwilkerson) Should this function also return true for valid
// top-level keywords?
bool isKeywordAfterUri(Token token) =>
token.lexeme == Keyword.AS.lexeme ||
token.lexeme == _HIDE ||
token.lexeme == _SHOW;
TokenType type = _currentToken.type;
if (type != TokenType.STRING &&
type != TokenType.SEMICOLON &&
!isKeywordAfterUri(_currentToken)) {
// Attempt to recover in the case where the URI was not enclosed in
// quotes.
Token token = _currentToken;
bool isValidInUri(Token token) {
TokenType type = token.type;
return type == TokenType.COLON ||
type == TokenType.SLASH ||
type == TokenType.PERIOD ||
type == TokenType.PERIOD_PERIOD ||
type == TokenType.PERIOD_PERIOD_PERIOD ||
type == TokenType.INT ||
type == TokenType.DOUBLE;
}
while ((_tokenMatchesIdentifier(token) && !isKeywordAfterUri(token)) ||
isValidInUri(token)) {
token = token.next;
}
if (_tokenMatches(token, TokenType.SEMICOLON) ||
isKeywordAfterUri(token)) {
Token endToken = token.previous;
token = _currentToken;
int endOffset = token.end;
StringBuffer buffer = new StringBuffer();
buffer.write(token.lexeme);
while (token != endToken) {
token = token.next;
if (token.offset != endOffset || token.precedingComments != null) {
return parseStringLiteral();
}
buffer.write(token.lexeme);
endOffset = token.end;
}
String value = buffer.toString();
Token newToken =
new StringToken(TokenType.STRING, "'$value'", _currentToken.offset);
_reportErrorForToken(
ParserErrorCode.NON_STRING_LITERAL_AS_URI, newToken);
_currentToken = endToken.next;
return astFactory.simpleStringLiteral(newToken, value);
}
}
return parseStringLiteral();
}
/// Parse a variable declaration statement. The [commentAndMetadata] is the
/// metadata to be associated with the variable declaration statement, or
/// `null` if there is no attempt at parsing the comment and metadata. The
/// [keyword] is the token representing the 'final', 'const' or 'var' keyword,
/// or `null` if there is no keyword. The [type] is the type of the variables
/// in the list. Return the variable declaration statement that was parsed.
///
/// variableDeclarationStatement ::=
/// variableDeclarationList ';'
VariableDeclarationStatement _parseVariableDeclarationStatementAfterType(
CommentAndMetadata commentAndMetadata,
Token keyword,
TypeAnnotation type) {
VariableDeclarationList variableList =
parseVariableDeclarationListAfterType(
commentAndMetadata, keyword, type);
Token semicolon = _expect(TokenType.SEMICOLON);
return astFactory.variableDeclarationStatement(variableList, semicolon);
}
/// Return the token that is immediately after the current token. This is
/// equivalent to [_peekAt](1).
Token _peek() => _currentToken.next;
/// Return the token that is the given [distance] after the current token,
/// where the distance is the number of tokens to look ahead. A distance of
/// `0` is the current token, `1` is the next token, etc.
Token _peekAt(int distance) {
Token token = _currentToken;
for (int i = 0; i < distance; i++) {
token = token.next;
}
return token;
}
String _removeGitHubInlineCode(String comment) {
int index = 0;
while (true) {
int beginIndex = comment.indexOf('`', index);
if (beginIndex == -1) {
break;
}
int endIndex = comment.indexOf('`', beginIndex + 1);
if (endIndex == -1) {
break;
}
comment = comment.substring(0, beginIndex + 1) +
' ' * (endIndex - beginIndex - 1) +
comment.substring(endIndex);
index = endIndex + 1;
}
return comment;
}
/// Report the given [error].
void _reportError(AnalysisError error) {
if (_errorListenerLock != 0) {
return;
}
_errorListener.onError(error);
}
/// Report an error with the given [errorCode] and [arguments] associated with
/// the current token.
void _reportErrorForCurrentToken(ParserErrorCode errorCode,
[List<Object> arguments]) {
_reportErrorForToken(errorCode, _currentToken, arguments);
}
/// Report an error with the given [errorCode] and [arguments] associated with
/// the given [node].
void _reportErrorForNode(ParserErrorCode errorCode, AstNode node,
[List<Object> arguments]) {
_reportError(new AnalysisError(
_source, node.offset, node.length, errorCode, arguments));
}
/// Report an error with the given [errorCode] and [arguments] associated with
/// the given [token].
void _reportErrorForToken(ErrorCode errorCode, Token token,
[List<Object> arguments]) {
if (token.type == TokenType.EOF) {
token = token.previous;
}
_reportError(new AnalysisError(_source, token.offset,
math.max(token.length, 1), errorCode, arguments));
}
/// Skips a block with all containing blocks.
void _skipBlock() {
Token endToken = (_currentToken as BeginToken).endToken;
if (endToken == null) {
endToken = _currentToken.next;
while (!identical(endToken, _currentToken)) {
_currentToken = endToken;
endToken = _currentToken.next;
}
_reportErrorForToken(
ParserErrorCode.EXPECTED_TOKEN, _currentToken.previous, ["}"]);
} else {
_currentToken = endToken.next;
}
}
/// Parse the 'final', 'const', 'var' or type preceding a variable
/// declaration, starting at the given token, without actually creating a
/// type or changing the current token. Return the token following the type
/// that was parsed, or `null` if the given token is not the first token in a
/// valid type. The [startToken] is the token at which parsing is to begin.
/// Return the token following the type that was parsed.
///
/// finalConstVarOrType ::=
/// | 'final' type?
/// | 'const' type?
/// | 'var'
/// | type
Token _skipFinalConstVarOrType(Token startToken) {
Keyword keyword = startToken.keyword;
if (keyword == Keyword.FINAL || keyword == Keyword.CONST) {
Token next = startToken.next;
if (_tokenMatchesIdentifier(next)) {
Token next2 = next.next;
// "Type parameter" or "Type<" or "prefix.Type"
if (_tokenMatchesIdentifier(next2) ||
_tokenMatches(next2, TokenType.LT) ||
_tokenMatches(next2, TokenType.PERIOD)) {
return skipTypeName(next);
}
// "parameter"
return next;
}
} else if (keyword == Keyword.VAR) {
return startToken.next;
} else if (_tokenMatchesIdentifier(startToken)) {
Token next = startToken.next;
if (_tokenMatchesIdentifier(next) ||
_tokenMatches(next, TokenType.LT) ||
_tokenMatchesKeyword(next, Keyword.THIS) ||
(_tokenMatches(next, TokenType.PERIOD) &&
_tokenMatchesIdentifier(next.next) &&
(_tokenMatchesIdentifier(next.next.next) ||
_tokenMatches(next.next.next, TokenType.LT) ||
_tokenMatchesKeyword(next.next.next, Keyword.THIS)))) {
return skipTypeAnnotation(startToken);
}
}
return null;
}
/// Parse a list of formal parameters, starting at the [startToken], without
/// actually creating a formal parameter list or changing the current token.
/// Return the token following the formal parameter list that was parsed, or
/// `null` if the given token is not the first token in a valid list of formal
/// parameter.
///
/// Note that unlike other skip methods, this method uses a heuristic. In the
/// worst case, the parameters could be prefixed by metadata, which would
/// require us to be able to skip arbitrary expressions. Rather than duplicate
/// the logic of most of the parse methods we simply look for something that
/// is likely to be a list of parameters and then skip to returning the token
/// after the closing parenthesis.
///
/// This method must be kept in sync with [parseFormalParameterList].
///
/// formalParameterList ::=
/// '(' ')'
/// | '(' normalFormalParameters (',' optionalFormalParameters)? ')'
/// | '(' optionalFormalParameters ')'
///
/// normalFormalParameters ::=
/// normalFormalParameter (',' normalFormalParameter)*
///
/// optionalFormalParameters ::=
/// optionalPositionalFormalParameters
/// | namedFormalParameters
///
/// optionalPositionalFormalParameters ::=
/// '[' defaultFormalParameter (',' defaultFormalParameter)* ']'
///
/// namedFormalParameters ::=
/// '{' defaultNamedParameter (',' defaultNamedParameter)* '}'
Token _skipFormalParameterList(Token startToken) {
if (!_tokenMatches(startToken, TokenType.OPEN_PAREN)) {
return null;
}
Token next = startToken.next;
if (_tokenMatches(next, TokenType.CLOSE_PAREN)) {
return next.next;
}
//
// Look to see whether the token after the open parenthesis is something
// that should only occur at the beginning of a parameter list.
//
if (next.matchesAny(const <TokenType>[
TokenType.AT,
TokenType.OPEN_SQUARE_BRACKET,
TokenType.OPEN_CURLY_BRACKET
]) ||
_tokenMatchesKeyword(next, Keyword.VOID) ||
(_tokenMatchesIdentifier(next) &&
(next.next.matchesAny(
const <TokenType>[TokenType.COMMA, TokenType.CLOSE_PAREN])))) {
return _skipPastMatchingToken(startToken);
}
//
// Look to see whether the first parameter is a function typed parameter
// without a return type.
//
if (_tokenMatchesIdentifier(next) &&
_tokenMatches(next.next, TokenType.OPEN_PAREN)) {
Token afterParameters = _skipFormalParameterList(next.next);
if (afterParameters != null &&
afterParameters.matchesAny(
const <TokenType>[TokenType.COMMA, TokenType.CLOSE_PAREN])) {
return _skipPastMatchingToken(startToken);
}
}
//
// Look to see whether the first parameter has a type or is a function typed
// parameter with a return type.
//
Token afterType = _skipFinalConstVarOrType(next);
if (afterType == null) {
return null;
}
if (skipSimpleIdentifier(afterType) == null) {
return null;
}
return _skipPastMatchingToken(startToken);
}
/// If the [startToken] is a begin token with an associated end token, then
/// return the token following the end token. Otherwise, return `null`.
Token _skipPastMatchingToken(Token startToken) {
if (startToken is! BeginToken) {
return null;
}
Token closeParen = (startToken as BeginToken).endToken;
if (closeParen == null) {
return null;
}
return closeParen.next;
}
/// Parse a string literal that contains interpolations, starting at the
/// [startToken], without actually creating a string literal or changing the
/// current token. Return the token following the string literal that was
/// parsed, or `null` if the given token is not the first token in a valid
/// string literal.
///
/// This method must be kept in sync with [parseStringInterpolation].
Token _skipStringInterpolation(Token startToken) {
Token token = startToken;
TokenType type = token.type;
while (type == TokenType.STRING_INTERPOLATION_EXPRESSION ||
type == TokenType.STRING_INTERPOLATION_IDENTIFIER) {
if (type == TokenType.STRING_INTERPOLATION_EXPRESSION) {
token = token.next;
type = token.type;
//
// Rather than verify that the following tokens represent a valid
// expression, we simply skip tokens until we reach the end of the
// interpolation, being careful to handle nested string literals.
//
int bracketNestingLevel = 1;
while (bracketNestingLevel > 0) {
if (type == TokenType.EOF) {
return null;
} else if (type == TokenType.OPEN_CURLY_BRACKET) {
bracketNestingLevel++;
token = token.next;
} else if (type == TokenType.CLOSE_CURLY_BRACKET) {
bracketNestingLevel--;
token = token.next;
} else if (type == TokenType.STRING) {
token = skipStringLiteral(token);
if (token == null) {
return null;
}
} else {
token = token.next;
}
type = token.type;
}
token = token.next;
type = token.type;
} else {
token = token.next;
if (token.type != TokenType.IDENTIFIER) {
return null;
}
token = token.next;
}
type = token.type;
if (type == TokenType.STRING) {
token = token.next;
type = token.type;
}
}
return token;
}
/// Parse a list of type parameters, starting at the [startToken], without
/// actually creating a type parameter list or changing the current token.
/// Return the token following the type parameter list that was parsed, or
/// `null` if the given token is not the first token in a valid type parameter
/// list.
///
/// This method must be kept in sync with [parseTypeParameterList].
///
/// typeParameterList ::=
/// '<' typeParameter (',' typeParameter)* '>'
Token _skipTypeParameterList(Token startToken) {
if (!_tokenMatches(startToken, TokenType.LT)) {
return null;
}
//
// We can't skip a type parameter because it can be preceeded by metadata,
// so we just assume that everything before the matching end token is valid.
//
int depth = 1;
Token next = startToken.next;
while (depth > 0) {
if (_tokenMatches(next, TokenType.EOF)) {
return null;
} else if (_tokenMatches(next, TokenType.LT)) {
depth++;
} else if (_tokenMatches(next, TokenType.GT)) {
depth--;
} else if (_tokenMatches(next, TokenType.GT_EQ)) {
if (depth == 1) {
Token fakeEquals = new Token(TokenType.EQ, next.offset + 2);
fakeEquals.setNextWithoutSettingPrevious(next.next);
return fakeEquals;
}
depth--;
} else if (_tokenMatches(next, TokenType.GT_GT)) {
depth -= 2;
} else if (_tokenMatches(next, TokenType.GT_GT_EQ)) {
if (depth < 2) {
return null;
} else if (depth == 2) {
Token fakeEquals = new Token(TokenType.EQ, next.offset + 2);
fakeEquals.setNextWithoutSettingPrevious(next.next);
return fakeEquals;
}
depth -= 2;
}
next = next.next;
}
return next;
}
/// Assuming that the current token is an index token ('[]'), split it into
/// two tokens ('[' and ']'), leaving the left bracket as the current token.
void _splitIndex() {
// Split the token into two separate tokens.
BeginToken leftBracket = _createToken(
_currentToken, TokenType.OPEN_SQUARE_BRACKET,
isBegin: true);
Token rightBracket =
new Token(TokenType.CLOSE_SQUARE_BRACKET, _currentToken.offset + 1);
leftBracket.endToken = rightBracket;
rightBracket.setNext(_currentToken.next);
leftBracket.setNext(rightBracket);
_currentToken.previous.setNext(leftBracket);
_currentToken = leftBracket;
}
/// Return `true` if the given [token] has the given [type].
bool _tokenMatches(Token token, TokenType type) => token.type == type;
/// Return `true` if the given [token] is a valid identifier. Valid
/// identifiers include built-in identifiers (pseudo-keywords).
bool _tokenMatchesIdentifier(Token token) =>
_tokenMatches(token, TokenType.IDENTIFIER) ||
_tokenMatchesPseudoKeyword(token);
/// Return `true` if the given [token] is either an identifier or a keyword.
bool _tokenMatchesIdentifierOrKeyword(Token token) =>
_tokenMatches(token, TokenType.IDENTIFIER) || token.type.isKeyword;
/// Return `true` if the given [token] matches the given [keyword].
bool _tokenMatchesKeyword(Token token, Keyword keyword) =>
token.keyword == keyword;
/// Return `true` if the given [token] matches a pseudo keyword.
bool _tokenMatchesPseudoKeyword(Token token) =>
token.keyword?.isBuiltInOrPseudo ?? false;
/// Translate the characters at the given [index] in the given [lexeme],
/// appending the translated character to the given [buffer]. The index is
/// assumed to be valid.
int _translateCharacter(StringBuffer buffer, String lexeme, int index) {
int currentChar = lexeme.codeUnitAt(index);
if (currentChar != 0x5C) {
buffer.writeCharCode(currentChar);
return index + 1;
}
//
// We have found an escape sequence, so we parse the string to determine
// what kind of escape sequence and what character to add to the builder.
//
int length = lexeme.length;
int currentIndex = index + 1;
if (currentIndex >= length) {
// Illegal escape sequence: no char after escape.
// This cannot actually happen because it would require the escape
// character to be the last character in the string, but if it were it
// would escape the closing quote, leaving the string unclosed.
// reportError(ParserErrorCode.MISSING_CHAR_IN_ESCAPE_SEQUENCE);
return length;
}
currentChar = lexeme.codeUnitAt(currentIndex);
if (currentChar == 0x6E) {
buffer.writeCharCode(0xA);
// newline
} else if (currentChar == 0x72) {
buffer.writeCharCode(0xD);
// carriage return
} else if (currentChar == 0x66) {
buffer.writeCharCode(0xC);
// form feed
} else if (currentChar == 0x62) {
buffer.writeCharCode(0x8);
// backspace
} else if (currentChar == 0x74) {
buffer.writeCharCode(0x9);
// tab
} else if (currentChar == 0x76) {
buffer.writeCharCode(0xB);
// vertical tab
} else if (currentChar == 0x78) {
if (currentIndex + 2 >= length) {
// Illegal escape sequence: not enough hex digits
_reportErrorForCurrentToken(ParserErrorCode.INVALID_HEX_ESCAPE);
return length;
}
int firstDigit = lexeme.codeUnitAt(currentIndex + 1);
int secondDigit = lexeme.codeUnitAt(currentIndex + 2);
if (!_isHexDigit(firstDigit) || !_isHexDigit(secondDigit)) {
// Illegal escape sequence: invalid hex digit
_reportErrorForCurrentToken(ParserErrorCode.INVALID_HEX_ESCAPE);
} else {
int charCode = (Character.digit(firstDigit, 16) << 4) +
Character.digit(secondDigit, 16);
buffer.writeCharCode(charCode);
}
return currentIndex + 3;
} else if (currentChar == 0x75) {
currentIndex++;
if (currentIndex >= length) {
// Illegal escape sequence: not enough hex digits
_reportErrorForCurrentToken(ParserErrorCode.INVALID_UNICODE_ESCAPE);
return length;
}
currentChar = lexeme.codeUnitAt(currentIndex);
if (currentChar == 0x7B) {
currentIndex++;
if (currentIndex >= length) {
// Illegal escape sequence: incomplete escape
_reportErrorForCurrentToken(ParserErrorCode.INVALID_UNICODE_ESCAPE);
return length;
}
currentChar = lexeme.codeUnitAt(currentIndex);
int digitCount = 0;
int value = 0;
while (currentChar != 0x7D) {
if (!_isHexDigit(currentChar)) {
// Illegal escape sequence: invalid hex digit
_reportErrorForCurrentToken(ParserErrorCode.INVALID_UNICODE_ESCAPE);
currentIndex++;
while (currentIndex < length &&
lexeme.codeUnitAt(currentIndex) != 0x7D) {
currentIndex++;
}
return currentIndex + 1;
}
digitCount++;
value = (value << 4) + Character.digit(currentChar, 16);
currentIndex++;
if (currentIndex >= length) {
// Illegal escape sequence: incomplete escape
_reportErrorForCurrentToken(ParserErrorCode.INVALID_UNICODE_ESCAPE);
return length;
}
currentChar = lexeme.codeUnitAt(currentIndex);
}
if (digitCount < 1 || digitCount > 6) {
// Illegal escape sequence: not enough or too many hex digits
_reportErrorForCurrentToken(ParserErrorCode.INVALID_UNICODE_ESCAPE);
}
_appendCodePoint(buffer, lexeme, value, index, currentIndex);
return currentIndex + 1;
} else {
if (currentIndex + 3 >= length) {
// Illegal escape sequence: not enough hex digits
_reportErrorForCurrentToken(ParserErrorCode.INVALID_UNICODE_ESCAPE);
return length;
}
int firstDigit = currentChar;
int secondDigit = lexeme.codeUnitAt(currentIndex + 1);
int thirdDigit = lexeme.codeUnitAt(currentIndex + 2);
int fourthDigit = lexeme.codeUnitAt(currentIndex + 3);
if (!_isHexDigit(firstDigit) ||
!_isHexDigit(secondDigit) ||
!_isHexDigit(thirdDigit) ||
!_isHexDigit(fourthDigit)) {
// Illegal escape sequence: invalid hex digits
_reportErrorForCurrentToken(ParserErrorCode.INVALID_UNICODE_ESCAPE);
} else {
_appendCodePoint(
buffer,
lexeme,
(((((Character.digit(firstDigit, 16) << 4) +
Character.digit(secondDigit, 16)) <<
4) +
Character.digit(thirdDigit, 16)) <<
4) +
Character.digit(fourthDigit, 16),
index,
currentIndex + 3);
}
return currentIndex + 4;
}
} else {
buffer.writeCharCode(currentChar);
}
return currentIndex + 1;
}
/// Decrements the error reporting lock level. If level is more than `0`, then
/// [reportError] wont report any error.
void _unlockErrorListener() {
if (_errorListenerLock == 0) {
throw new StateError("Attempt to unlock not locked error listener.");
}
_errorListenerLock--;
}
/// Validate that the given [parameterList] does not contain any field
/// initializers.
void _validateFormalParameterList(FormalParameterList parameterList) {
for (FormalParameter parameter in parameterList.parameters) {
if (parameter is FieldFormalParameter) {
_reportErrorForNode(
ParserErrorCode.FIELD_INITIALIZER_OUTSIDE_CONSTRUCTOR,
parameter.identifier);
}
}
}
/// Validate that the given set of [modifiers] is appropriate for a class and
/// return the 'abstract' keyword if there is one.
Token _validateModifiersForClass(Modifiers modifiers) {
_validateModifiersForTopLevelDeclaration(modifiers);
if (modifiers.constKeyword != null) {
_reportErrorForToken(ParserErrorCode.CONST_CLASS, modifiers.constKeyword);
}
if (modifiers.externalKeyword != null) {
_reportErrorForToken(
ParserErrorCode.EXTERNAL_CLASS, modifiers.externalKeyword);
}
if (modifiers.finalKeyword != null) {
_reportErrorForToken(ParserErrorCode.FINAL_CLASS, modifiers.finalKeyword);
}
if (modifiers.varKeyword != null) {
_reportErrorForToken(ParserErrorCode.VAR_CLASS, modifiers.varKeyword);
}
return modifiers.abstractKeyword;
}
/// Validate that the given set of [modifiers] is appropriate for a
/// constructor and return the 'const' keyword if there is one.
Token _validateModifiersForConstructor(Modifiers modifiers) {
if (modifiers.abstractKeyword != null) {
_reportErrorForToken(
ParserErrorCode.ABSTRACT_CLASS_MEMBER, modifiers.abstractKeyword);
}
if (modifiers.covariantKeyword != null) {
_reportErrorForToken(
ParserErrorCode.COVARIANT_CONSTRUCTOR, modifiers.covariantKeyword);
}
if (modifiers.finalKeyword != null) {
_reportErrorForToken(
ParserErrorCode.FINAL_CONSTRUCTOR, modifiers.finalKeyword);
}
if (modifiers.staticKeyword != null) {
_reportErrorForToken(
ParserErrorCode.STATIC_CONSTRUCTOR, modifiers.staticKeyword);
}
if (modifiers.varKeyword != null) {
_reportErrorForToken(
ParserErrorCode.CONSTRUCTOR_WITH_RETURN_TYPE, modifiers.varKeyword);
}
Token externalKeyword = modifiers.externalKeyword;
Token constKeyword = modifiers.constKeyword;
Token factoryKeyword = modifiers.factoryKeyword;
if (externalKeyword != null &&
constKeyword != null &&
constKeyword.offset < externalKeyword.offset) {
_reportErrorForToken(
ParserErrorCode.EXTERNAL_AFTER_CONST, externalKeyword);
}
if (externalKeyword != null &&
factoryKeyword != null &&
factoryKeyword.offset < externalKeyword.offset) {
_reportErrorForToken(
ParserErrorCode.EXTERNAL_AFTER_FACTORY, externalKeyword);
}
return constKeyword;
}
/// Validate that the given set of [modifiers] is appropriate for an enum and
/// return the 'abstract' keyword if there is one.
void _validateModifiersForEnum(Modifiers modifiers) {
_validateModifiersForTopLevelDeclaration(modifiers);
if (modifiers.abstractKeyword != null) {
_reportErrorForToken(
ParserErrorCode.ABSTRACT_ENUM, modifiers.abstractKeyword);
}
if (modifiers.constKeyword != null) {
_reportErrorForToken(ParserErrorCode.CONST_ENUM, modifiers.constKeyword);
}
if (modifiers.externalKeyword != null) {
_reportErrorForToken(
ParserErrorCode.EXTERNAL_ENUM, modifiers.externalKeyword);
}
if (modifiers.finalKeyword != null) {
_reportErrorForToken(ParserErrorCode.FINAL_ENUM, modifiers.finalKeyword);
}
if (modifiers.varKeyword != null) {
_reportErrorForToken(ParserErrorCode.VAR_ENUM, modifiers.varKeyword);
}
}
/// Validate that the given set of [modifiers] is appropriate for a field and
/// return the 'final', 'const' or 'var' keyword if there is one.
Token _validateModifiersForField(Modifiers modifiers) {
if (modifiers.abstractKeyword != null) {
_reportErrorForCurrentToken(ParserErrorCode.ABSTRACT_CLASS_MEMBER);
}
if (modifiers.externalKeyword != null) {
_reportErrorForToken(
ParserErrorCode.EXTERNAL_FIELD, modifiers.externalKeyword);
}
if (modifiers.factoryKeyword != null) {
_reportErrorForToken(
ParserErrorCode.NON_CONSTRUCTOR_FACTORY, modifiers.factoryKeyword);
}
Token staticKeyword = modifiers.staticKeyword;
Token covariantKeyword = modifiers.covariantKeyword;
Token constKeyword = modifiers.constKeyword;
Token finalKeyword = modifiers.finalKeyword;
Token varKeyword = modifiers.varKeyword;
if (constKeyword != null) {
if (covariantKeyword != null) {
_reportErrorForToken(
ParserErrorCode.CONST_AND_COVARIANT, covariantKeyword);
}
if (finalKeyword != null) {
_reportErrorForToken(ParserErrorCode.CONST_AND_FINAL, finalKeyword);
}
if (varKeyword != null) {
_reportErrorForToken(ParserErrorCode.CONST_AND_VAR, varKeyword);
}
if (staticKeyword != null && constKeyword.offset < staticKeyword.offset) {
_reportErrorForToken(ParserErrorCode.STATIC_AFTER_CONST, staticKeyword);
}
} else if (finalKeyword != null) {
if (covariantKeyword != null) {
_reportErrorForToken(
ParserErrorCode.FINAL_AND_COVARIANT, covariantKeyword);
}
if (varKeyword != null) {
_reportErrorForToken(ParserErrorCode.FINAL_AND_VAR, varKeyword);
}
if (staticKeyword != null && finalKeyword.offset < staticKeyword.offset) {
_reportErrorForToken(ParserErrorCode.STATIC_AFTER_FINAL, staticKeyword);
}
} else if (varKeyword != null) {
if (staticKeyword != null && varKeyword.offset < staticKeyword.offset) {
_reportErrorForToken(ParserErrorCode.STATIC_AFTER_VAR, staticKeyword);
}
if (covariantKeyword != null &&
varKeyword.offset < covariantKeyword.offset) {
_reportErrorForToken(
ParserErrorCode.COVARIANT_AFTER_VAR, covariantKeyword);
}
}
if (covariantKeyword != null && staticKeyword != null) {
_reportErrorForToken(ParserErrorCode.COVARIANT_AND_STATIC, staticKeyword);
}
return Token.lexicallyFirst([constKeyword, finalKeyword, varKeyword]);
}
/// Validate that the given set of [modifiers] is appropriate for a local
/// function.
void _validateModifiersForFunctionDeclarationStatement(Modifiers modifiers) {
if (modifiers.abstractKeyword != null ||
modifiers.constKeyword != null ||
modifiers.externalKeyword != null ||
modifiers.factoryKeyword != null ||
modifiers.finalKeyword != null ||
modifiers.staticKeyword != null ||
modifiers.varKeyword != null) {
_reportErrorForCurrentToken(
ParserErrorCode.LOCAL_FUNCTION_DECLARATION_MODIFIER);
}
}
/// Validate that the given set of [modifiers] is appropriate for a getter,
/// setter, or method.
void _validateModifiersForGetterOrSetterOrMethod(Modifiers modifiers) {
if (modifiers.abstractKeyword != null) {
_reportErrorForCurrentToken(ParserErrorCode.ABSTRACT_CLASS_MEMBER);
}
if (modifiers.constKeyword != null) {
_reportErrorForToken(
ParserErrorCode.CONST_METHOD, modifiers.constKeyword);
}
if (modifiers.covariantKeyword != null) {
_reportErrorForToken(
ParserErrorCode.COVARIANT_MEMBER, modifiers.covariantKeyword);
}
if (modifiers.factoryKeyword != null) {
_reportErrorForToken(
ParserErrorCode.NON_CONSTRUCTOR_FACTORY, modifiers.factoryKeyword);
}
if (modifiers.finalKeyword != null) {
_reportErrorForToken(
ParserErrorCode.FINAL_METHOD, modifiers.finalKeyword);
}
if (modifiers.varKeyword != null) {
_reportErrorForToken(
ParserErrorCode.VAR_RETURN_TYPE, modifiers.varKeyword);
}
Token externalKeyword = modifiers.externalKeyword;
Token staticKeyword = modifiers.staticKeyword;
if (externalKeyword != null &&
staticKeyword != null &&
staticKeyword.offset < externalKeyword.offset) {
_reportErrorForToken(
ParserErrorCode.EXTERNAL_AFTER_STATIC, externalKeyword);
}
}
/// Validate that the given set of [modifiers] is appropriate for a getter,
/// setter, or method.
void _validateModifiersForOperator(Modifiers modifiers) {
if (modifiers.abstractKeyword != null) {
_reportErrorForCurrentToken(ParserErrorCode.ABSTRACT_CLASS_MEMBER);
}
if (modifiers.constKeyword != null) {
_reportErrorForToken(
ParserErrorCode.CONST_METHOD, modifiers.constKeyword);
}
if (modifiers.factoryKeyword != null) {
_reportErrorForToken(
ParserErrorCode.NON_CONSTRUCTOR_FACTORY, modifiers.factoryKeyword);
}
if (modifiers.finalKeyword != null) {
_reportErrorForToken(
ParserErrorCode.FINAL_METHOD, modifiers.finalKeyword);
}
if (modifiers.staticKeyword != null) {
_reportErrorForToken(
ParserErrorCode.STATIC_OPERATOR, modifiers.staticKeyword);
}
if (modifiers.varKeyword != null) {
_reportErrorForToken(
ParserErrorCode.VAR_RETURN_TYPE, modifiers.varKeyword);
}
}
/// Validate that the given set of [modifiers] is appropriate for a top-level
/// declaration.
void _validateModifiersForTopLevelDeclaration(Modifiers modifiers) {
if (modifiers.covariantKeyword != null) {
_reportErrorForToken(ParserErrorCode.COVARIANT_TOP_LEVEL_DECLARATION,
modifiers.covariantKeyword);
}
if (modifiers.factoryKeyword != null) {
_reportErrorForToken(ParserErrorCode.FACTORY_TOP_LEVEL_DECLARATION,
modifiers.factoryKeyword);
}
if (modifiers.staticKeyword != null) {
_reportErrorForToken(ParserErrorCode.STATIC_TOP_LEVEL_DECLARATION,
modifiers.staticKeyword);
}
}
/// Validate that the given set of [modifiers] is appropriate for a top-level
/// function.
void _validateModifiersForTopLevelFunction(Modifiers modifiers) {
_validateModifiersForTopLevelDeclaration(modifiers);
if (modifiers.abstractKeyword != null) {
_reportErrorForCurrentToken(ParserErrorCode.ABSTRACT_TOP_LEVEL_FUNCTION);
}
if (modifiers.constKeyword != null) {
_reportErrorForToken(ParserErrorCode.CONST_CLASS, modifiers.constKeyword);
}
if (modifiers.finalKeyword != null) {
_reportErrorForToken(ParserErrorCode.FINAL_CLASS, modifiers.finalKeyword);
}
if (modifiers.varKeyword != null) {
_reportErrorForToken(
ParserErrorCode.VAR_RETURN_TYPE, modifiers.varKeyword);
}
}
/// Validate that the given set of [modifiers] is appropriate for a field and
/// return the 'final', 'const' or 'var' keyword if there is one.
Token _validateModifiersForTopLevelVariable(Modifiers modifiers) {
_validateModifiersForTopLevelDeclaration(modifiers);
if (modifiers.abstractKeyword != null) {
_reportErrorForCurrentToken(ParserErrorCode.ABSTRACT_TOP_LEVEL_VARIABLE);
}
if (modifiers.externalKeyword != null) {
_reportErrorForToken(
ParserErrorCode.EXTERNAL_FIELD, modifiers.externalKeyword);
}
Token constKeyword = modifiers.constKeyword;
Token finalKeyword = modifiers.finalKeyword;
Token varKeyword = modifiers.varKeyword;
if (constKeyword != null) {
if (finalKeyword != null) {
_reportErrorForToken(ParserErrorCode.CONST_AND_FINAL, finalKeyword);
}
if (varKeyword != null) {
_reportErrorForToken(ParserErrorCode.CONST_AND_VAR, varKeyword);
}
} else if (finalKeyword != null) {
if (varKeyword != null) {
_reportErrorForToken(ParserErrorCode.FINAL_AND_VAR, varKeyword);
}
}
return Token.lexicallyFirst([constKeyword, finalKeyword, varKeyword]);
}
/// Validate that the given set of [modifiers] is appropriate for a class and
/// return the 'abstract' keyword if there is one.
void _validateModifiersForTypedef(Modifiers modifiers) {
_validateModifiersForTopLevelDeclaration(modifiers);
if (modifiers.abstractKeyword != null) {
_reportErrorForToken(
ParserErrorCode.ABSTRACT_TYPEDEF, modifiers.abstractKeyword);
}
if (modifiers.constKeyword != null) {
_reportErrorForToken(
ParserErrorCode.CONST_TYPEDEF, modifiers.constKeyword);
}
if (modifiers.externalKeyword != null) {
_reportErrorForToken(
ParserErrorCode.EXTERNAL_TYPEDEF, modifiers.externalKeyword);
}
if (modifiers.finalKeyword != null) {
_reportErrorForToken(
ParserErrorCode.FINAL_TYPEDEF, modifiers.finalKeyword);
}
if (modifiers.varKeyword != null) {
_reportErrorForToken(ParserErrorCode.VAR_TYPEDEF, modifiers.varKeyword);
}
}
}
/// Instances of this class are thrown when the parser detects that AST has
/// too many nested expressions to be parsed safely and avoid possibility of
/// [StackOverflowError] in the parser or during later analysis.
class _TooDeepTreeError {}