| Index: pkg/dart_parser/lib/src/parser.dart
|
| diff --git a/pkg/dart_parser/lib/src/parser.dart b/pkg/dart_parser/lib/src/parser.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..3cac0379d321f2d2565c3c07e1b668d5e8aaf2b9
|
| --- /dev/null
|
| +++ b/pkg/dart_parser/lib/src/parser.dart
|
| @@ -0,0 +1,3014 @@
|
| +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +library dart2js.parser;
|
| +
|
| +import '../common.dart';
|
| +import '../options.dart' show ParserOptions;
|
| +import '../tokens/keyword.dart' show Keyword;
|
| +import '../tokens/precedence.dart' show PrecedenceInfo;
|
| +import '../tokens/precedence_constants.dart'
|
| + show
|
| + AS_INFO,
|
| + ASSIGNMENT_PRECEDENCE,
|
| + CASCADE_PRECEDENCE,
|
| + EQUALITY_PRECEDENCE,
|
| + GT_INFO,
|
| + IS_INFO,
|
| + MINUS_MINUS_INFO,
|
| + OPEN_PAREN_INFO,
|
| + OPEN_SQUARE_BRACKET_INFO,
|
| + PERIOD_INFO,
|
| + PLUS_PLUS_INFO,
|
| + POSTFIX_PRECEDENCE,
|
| + QUESTION_INFO,
|
| + QUESTION_PERIOD_INFO,
|
| + RELATIONAL_PRECEDENCE;
|
| +import '../tokens/token.dart'
|
| + show
|
| + BeginGroupToken,
|
| + isUserDefinableOperator,
|
| + KeywordToken,
|
| + SymbolToken,
|
| + Token;
|
| +import '../tokens/token_constants.dart'
|
| + show
|
| + BAD_INPUT_TOKEN,
|
| + COMMA_TOKEN,
|
| + DOUBLE_TOKEN,
|
| + EOF_TOKEN,
|
| + EQ_TOKEN,
|
| + FUNCTION_TOKEN,
|
| + GT_TOKEN,
|
| + GT_GT_TOKEN,
|
| + HASH_TOKEN,
|
| + HEXADECIMAL_TOKEN,
|
| + IDENTIFIER_TOKEN,
|
| + INT_TOKEN,
|
| + KEYWORD_TOKEN,
|
| + LT_TOKEN,
|
| + OPEN_CURLY_BRACKET_TOKEN,
|
| + OPEN_PAREN_TOKEN,
|
| + OPEN_SQUARE_BRACKET_TOKEN,
|
| + PERIOD_TOKEN,
|
| + SEMICOLON_TOKEN,
|
| + STRING_INTERPOLATION_IDENTIFIER_TOKEN,
|
| + STRING_INTERPOLATION_TOKEN,
|
| + STRING_TOKEN;
|
| +import '../util/characters.dart' as Characters show $CLOSE_CURLY_BRACKET;
|
| +import '../util/util.dart' show Link;
|
| +import 'listener.dart' show Listener;
|
| +
|
| +class FormalParameterType {
|
| + final String type;
|
| + const FormalParameterType(this.type);
|
| + bool get isRequired => this == REQUIRED;
|
| + bool get isPositional => this == POSITIONAL;
|
| + bool get isNamed => this == NAMED;
|
| + static final REQUIRED = const FormalParameterType('required');
|
| + static final POSITIONAL = const FormalParameterType('positional');
|
| + static final NAMED = const FormalParameterType('named');
|
| +}
|
| +
|
| +/**
|
| + * An event generating parser of Dart programs. This parser expects
|
| + * all tokens in a linked list (aka a token stream).
|
| + *
|
| + * The class [Scanner] is used to generate a token stream. See the
|
| + * file scanner.dart.
|
| + *
|
| + * Subclasses of the class [Listener] are used to listen to events.
|
| + *
|
| + * Most methods of this class belong in one of two major categories:
|
| + * parse metods and peek methods. Parse methods all have the prefix
|
| + * parse, and peek methods all have the prefix peek.
|
| + *
|
| + * Parse methods generate events (by calling methods on [listener])
|
| + * and return the next token to parse. Peek methods do not generate
|
| + * events (except for errors) and may return null.
|
| + *
|
| + * Parse methods are generally named parseGrammarProductionSuffix. The
|
| + * suffix can be one of "opt", or "star". "opt" means zero or one
|
| + * matches, "star" means zero or more matches. For example,
|
| + * [parseMetadataStar] corresponds to this grammar snippet: [:
|
| + * metadata* :], and [parseTypeOpt] corresponds to: [: type? :].
|
| + */
|
| +class Parser {
|
| + final Listener listener;
|
| + final ParserOptions parserOptions;
|
| + bool mayParseFunctionExpressions = true;
|
| + bool asyncAwaitKeywordsEnabled;
|
| +
|
| + final bool enableGenericMethodSyntax;
|
| +
|
| + Parser(this.listener, ParserOptions parserOptions,
|
| + {this.asyncAwaitKeywordsEnabled: false})
|
| + : parserOptions = parserOptions,
|
| + enableGenericMethodSyntax = parserOptions.enableGenericMethodSyntax;
|
| +
|
| + Token parseUnit(Token token) {
|
| + listener.beginCompilationUnit(token);
|
| + int count = 0;
|
| + while (!identical(token.kind, EOF_TOKEN)) {
|
| + token = parseTopLevelDeclaration(token);
|
| + count++;
|
| + }
|
| + listener.endCompilationUnit(count, token);
|
| + return token;
|
| + }
|
| +
|
| + Token parseTopLevelDeclaration(Token token) {
|
| + token = _parseTopLevelDeclaration(token);
|
| + listener.endTopLevelDeclaration(token);
|
| + return token;
|
| + }
|
| +
|
| + Token _parseTopLevelDeclaration(Token token) {
|
| + token = parseMetadataStar(token);
|
| + final String value = token.stringValue;
|
| + if ((identical(value, 'abstract') && optional('class', token.next)) ||
|
| + identical(value, 'class')) {
|
| + return parseClassOrNamedMixinApplication(token);
|
| + } else if (identical(value, 'enum')) {
|
| + return parseEnum(token);
|
| + } else if (identical(value, 'typedef')) {
|
| + return parseTypedef(token);
|
| + } else if (identical(value, 'library')) {
|
| + return parseLibraryName(token);
|
| + } else if (identical(value, 'import')) {
|
| + return parseImport(token);
|
| + } else if (identical(value, 'export')) {
|
| + return parseExport(token);
|
| + } else if (identical(value, 'part')) {
|
| + return parsePartOrPartOf(token);
|
| + } else {
|
| + return parseTopLevelMember(token);
|
| + }
|
| + }
|
| +
|
| + /// library qualified ';'
|
| + Token parseLibraryName(Token token) {
|
| + Token libraryKeyword = token;
|
| + listener.beginLibraryName(libraryKeyword);
|
| + assert(optional('library', token));
|
| + token = parseQualified(token.next);
|
| + Token semicolon = token;
|
| + token = expect(';', token);
|
| + listener.endLibraryName(libraryKeyword, semicolon);
|
| + return token;
|
| + }
|
| +
|
| + /// import uri (if (test) uri)* (as identifier)? combinator* ';'
|
| + Token parseImport(Token token) {
|
| + Token importKeyword = token;
|
| + listener.beginImport(importKeyword);
|
| + assert(optional('import', token));
|
| + token = parseLiteralStringOrRecoverExpression(token.next);
|
| + token = parseConditionalUris(token);
|
| + Token deferredKeyword;
|
| + if (optional('deferred', token)) {
|
| + deferredKeyword = token;
|
| + token = token.next;
|
| + }
|
| + Token asKeyword;
|
| + if (optional('as', token)) {
|
| + asKeyword = token;
|
| + token = parseIdentifier(token.next);
|
| + }
|
| + token = parseCombinators(token);
|
| + Token semicolon = token;
|
| + token = expect(';', token);
|
| + listener.endImport(importKeyword, deferredKeyword, asKeyword, semicolon);
|
| + return token;
|
| + }
|
| +
|
| + /// if (test) uri
|
| + Token parseConditionalUris(Token token) {
|
| + listener.beginConditionalUris(token);
|
| + int count = 0;
|
| + while (optional('if', token)) {
|
| + count++;
|
| + token = parseConditionalUri(token);
|
| + }
|
| + listener.endConditionalUris(count);
|
| + return token;
|
| + }
|
| +
|
| + Token parseConditionalUri(Token token) {
|
| + listener.beginConditionalUri(token);
|
| + Token ifKeyword = token;
|
| + token = expect('if', token);
|
| + token = expect('(', token);
|
| + token = parseDottedName(token);
|
| + Token equalitySign;
|
| + if (optional('==', token)) {
|
| + equalitySign = token;
|
| + token = parseLiteralStringOrRecoverExpression(token.next);
|
| + }
|
| + token = expect(')', token);
|
| + token = parseLiteralStringOrRecoverExpression(token);
|
| + listener.endConditionalUri(ifKeyword, equalitySign);
|
| + return token;
|
| + }
|
| +
|
| + Token parseDottedName(Token token) {
|
| + listener.beginDottedName(token);
|
| + Token firstIdentifier = token;
|
| + token = parseIdentifier(token);
|
| + int count = 1;
|
| + while (optional('.', token)) {
|
| + token = parseIdentifier(token.next);
|
| + count++;
|
| + }
|
| + listener.endDottedName(count, firstIdentifier);
|
| + return token;
|
| + }
|
| +
|
| + /// export uri conditional-uris* combinator* ';'
|
| + Token parseExport(Token token) {
|
| + Token exportKeyword = token;
|
| + listener.beginExport(exportKeyword);
|
| + assert(optional('export', token));
|
| + token = parseLiteralStringOrRecoverExpression(token.next);
|
| + token = parseConditionalUris(token);
|
| + token = parseCombinators(token);
|
| + Token semicolon = token;
|
| + token = expect(';', token);
|
| + listener.endExport(exportKeyword, semicolon);
|
| + return token;
|
| + }
|
| +
|
| + Token parseCombinators(Token token) {
|
| + listener.beginCombinators(token);
|
| + int count = 0;
|
| + while (true) {
|
| + String value = token.stringValue;
|
| + if (identical('hide', value)) {
|
| + token = parseHide(token);
|
| + } else if (identical('show', value)) {
|
| + token = parseShow(token);
|
| + } else {
|
| + listener.endCombinators(count);
|
| + break;
|
| + }
|
| + count++;
|
| + }
|
| + return token;
|
| + }
|
| +
|
| + /// hide identifierList
|
| + Token parseHide(Token token) {
|
| + Token hideKeyword = token;
|
| + listener.beginHide(hideKeyword);
|
| + assert(optional('hide', token));
|
| + token = parseIdentifierList(token.next);
|
| + listener.endHide(hideKeyword);
|
| + return token;
|
| + }
|
| +
|
| + /// show identifierList
|
| + Token parseShow(Token token) {
|
| + Token showKeyword = token;
|
| + listener.beginShow(showKeyword);
|
| + assert(optional('show', token));
|
| + token = parseIdentifierList(token.next);
|
| + listener.endShow(showKeyword);
|
| + return token;
|
| + }
|
| +
|
| + /// identifier (, identifier)*
|
| + Token parseIdentifierList(Token token) {
|
| + listener.beginIdentifierList(token);
|
| + token = parseIdentifier(token);
|
| + int count = 1;
|
| + while (optional(',', token)) {
|
| + token = parseIdentifier(token.next);
|
| + count++;
|
| + }
|
| + listener.endIdentifierList(count);
|
| + return token;
|
| + }
|
| +
|
| + /// type (, type)*
|
| + Token parseTypeList(Token token) {
|
| + listener.beginTypeList(token);
|
| + token = parseType(token);
|
| + int count = 1;
|
| + while (optional(',', token)) {
|
| + token = parseType(token.next);
|
| + count++;
|
| + }
|
| + listener.endTypeList(count);
|
| + return token;
|
| + }
|
| +
|
| + Token parsePartOrPartOf(Token token) {
|
| + assert(optional('part', token));
|
| + if (optional('of', token.next)) {
|
| + return parsePartOf(token);
|
| + } else {
|
| + return parsePart(token);
|
| + }
|
| + }
|
| +
|
| + Token parsePart(Token token) {
|
| + Token partKeyword = token;
|
| + listener.beginPart(token);
|
| + assert(optional('part', token));
|
| + token = parseLiteralStringOrRecoverExpression(token.next);
|
| + Token semicolon = token;
|
| + token = expect(';', token);
|
| + listener.endPart(partKeyword, semicolon);
|
| + return token;
|
| + }
|
| +
|
| + Token parsePartOf(Token token) {
|
| + listener.beginPartOf(token);
|
| + assert(optional('part', token));
|
| + assert(optional('of', token.next));
|
| + Token partKeyword = token;
|
| + token = parseQualified(token.next.next);
|
| + Token semicolon = token;
|
| + token = expect(';', token);
|
| + listener.endPartOf(partKeyword, semicolon);
|
| + return token;
|
| + }
|
| +
|
| + Token parseMetadataStar(Token token, {bool forParameter: false}) {
|
| + listener.beginMetadataStar(token);
|
| + int count = 0;
|
| + while (optional('@', token)) {
|
| + token = parseMetadata(token);
|
| + count++;
|
| + }
|
| + listener.endMetadataStar(count, forParameter);
|
| + return token;
|
| + }
|
| +
|
| + /**
|
| + * Parse
|
| + * [: '@' qualified (‘.’ identifier)? (arguments)? :]
|
| + */
|
| + Token parseMetadata(Token token) {
|
| + listener.beginMetadata(token);
|
| + Token atToken = token;
|
| + assert(optional('@', token));
|
| + token = parseIdentifier(token.next);
|
| + token = parseQualifiedRestOpt(token);
|
| + token = parseTypeArgumentsOpt(token);
|
| + Token period = null;
|
| + if (optional('.', token)) {
|
| + period = token;
|
| + token = parseIdentifier(token.next);
|
| + }
|
| + token = parseArgumentsOpt(token);
|
| + listener.endMetadata(atToken, period, token);
|
| + return token;
|
| + }
|
| +
|
| + Token parseTypedef(Token token) {
|
| + Token typedefKeyword = token;
|
| + if (optional('=', peekAfterType(token.next))) {
|
| + // TODO(aprelev@gmail.com): Remove deprecated 'typedef' mixin application,
|
| + // remove corresponding diagnostic from members.dart.
|
| + listener.beginNamedMixinApplication(token);
|
| + token = parseIdentifier(token.next);
|
| + token = parseTypeVariablesOpt(token);
|
| + token = expect('=', token);
|
| + token = parseModifiers(token);
|
| + token = parseMixinApplication(token);
|
| + Token implementsKeyword = null;
|
| + if (optional('implements', token)) {
|
| + implementsKeyword = token;
|
| + token = parseTypeList(token.next);
|
| + }
|
| + listener.endNamedMixinApplication(
|
| + typedefKeyword, implementsKeyword, token);
|
| + } else {
|
| + listener.beginFunctionTypeAlias(token);
|
| + token = parseReturnTypeOpt(token.next);
|
| + token = parseIdentifier(token);
|
| + token = parseTypeVariablesOpt(token);
|
| + token = parseFormalParameters(token);
|
| + listener.endFunctionTypeAlias(typedefKeyword, token);
|
| + }
|
| + return expect(';', token);
|
| + }
|
| +
|
| + Token parseMixinApplication(Token token) {
|
| + listener.beginMixinApplication(token);
|
| + token = parseType(token);
|
| + token = expect('with', token);
|
| + token = parseTypeList(token);
|
| + listener.endMixinApplication();
|
| + return token;
|
| + }
|
| +
|
| + Token parseReturnTypeOpt(Token token) {
|
| + if (identical(token.stringValue, 'void')) {
|
| + listener.handleVoidKeyword(token);
|
| + return token.next;
|
| + } else {
|
| + return parseTypeOpt(token);
|
| + }
|
| + }
|
| +
|
| + Token parseFormalParametersOpt(Token token) {
|
| + if (optional('(', token)) {
|
| + return parseFormalParameters(token);
|
| + } else {
|
| + listener.handleNoFormalParameters(token);
|
| + return token;
|
| + }
|
| + }
|
| +
|
| + Token parseFormalParameters(Token token) {
|
| + Token begin = token;
|
| + listener.beginFormalParameters(begin);
|
| + expect('(', token);
|
| + int parameterCount = 0;
|
| + do {
|
| + token = token.next;
|
| + if (optional(')', token)) {
|
| + break;
|
| + }
|
| + ++parameterCount;
|
| + String value = token.stringValue;
|
| + if (identical(value, '[')) {
|
| + token = parseOptionalFormalParameters(token, false);
|
| + break;
|
| + } else if (identical(value, '{')) {
|
| + token = parseOptionalFormalParameters(token, true);
|
| + break;
|
| + }
|
| + token = parseFormalParameter(token, FormalParameterType.REQUIRED);
|
| + } while (optional(',', token));
|
| + listener.endFormalParameters(parameterCount, begin, token);
|
| + return expect(')', token);
|
| + }
|
| +
|
| + Token parseFormalParameter(Token token, FormalParameterType type) {
|
| + token = parseMetadataStar(token, forParameter: true);
|
| + listener.beginFormalParameter(token);
|
| + token = parseModifiers(token);
|
| + // TODO(ahe): Validate that there are formal parameters if void.
|
| + token = parseReturnTypeOpt(token);
|
| + Token thisKeyword = null;
|
| + if (optional('this', token)) {
|
| + thisKeyword = token;
|
| + // TODO(ahe): Validate field initializers are only used in
|
| + // constructors, and not for function-typed arguments.
|
| + token = expect('.', token.next);
|
| + }
|
| + token = parseIdentifier(token);
|
| + if (optional('(', token)) {
|
| + listener.handleNoTypeVariables(token);
|
| + token = parseFormalParameters(token);
|
| + listener.handleFunctionTypedFormalParameter(token);
|
| + } else if (enableGenericMethodSyntax && optional('<', token)) {
|
| + token = parseTypeVariablesOpt(token);
|
| + token = parseFormalParameters(token);
|
| + listener.handleFunctionTypedFormalParameter(token);
|
| + }
|
| + String value = token.stringValue;
|
| + if ((identical('=', value)) || (identical(':', value))) {
|
| + // TODO(ahe): Validate that these are only used for optional parameters.
|
| + Token equal = token;
|
| + token = parseExpression(token.next);
|
| + listener.handleValuedFormalParameter(equal, token);
|
| + if (type.isRequired) {
|
| + listener.reportError(
|
| + equal, MessageKind.REQUIRED_PARAMETER_WITH_DEFAULT);
|
| + } else if (type.isPositional && identical(':', value)) {
|
| + listener.reportError(
|
| + equal, MessageKind.POSITIONAL_PARAMETER_WITH_EQUALS);
|
| + }
|
| + }
|
| + listener.endFormalParameter(thisKeyword);
|
| + return token;
|
| + }
|
| +
|
| + Token parseOptionalFormalParameters(Token token, bool isNamed) {
|
| + Token begin = token;
|
| + listener.beginOptionalFormalParameters(begin);
|
| + assert((isNamed && optional('{', token)) || optional('[', token));
|
| + int parameterCount = 0;
|
| + do {
|
| + token = token.next;
|
| + if (isNamed && optional('}', token)) {
|
| + break;
|
| + } else if (!isNamed && optional(']', token)) {
|
| + break;
|
| + }
|
| + var type =
|
| + isNamed ? FormalParameterType.NAMED : FormalParameterType.POSITIONAL;
|
| + token = parseFormalParameter(token, type);
|
| + ++parameterCount;
|
| + } while (optional(',', token));
|
| + if (parameterCount == 0) {
|
| + listener.reportError(
|
| + token,
|
| + isNamed
|
| + ? MessageKind.EMPTY_NAMED_PARAMETER_LIST
|
| + : MessageKind.EMPTY_OPTIONAL_PARAMETER_LIST);
|
| + }
|
| + listener.endOptionalFormalParameters(parameterCount, begin, token);
|
| + if (isNamed) {
|
| + return expect('}', token);
|
| + } else {
|
| + return expect(']', token);
|
| + }
|
| + }
|
| +
|
| + Token parseTypeOpt(Token token) {
|
| + Token peek = peekAfterIfType(token);
|
| + if (peek != null && (peek.isIdentifier() || optional('this', peek))) {
|
| + return parseType(token);
|
| + }
|
| + listener.handleNoType(token);
|
| + return token;
|
| + }
|
| +
|
| + bool isValidTypeReference(Token token) {
|
| + final kind = token.kind;
|
| + if (identical(kind, IDENTIFIER_TOKEN)) return true;
|
| + if (identical(kind, KEYWORD_TOKEN)) {
|
| + Keyword keyword = (token as KeywordToken).keyword;
|
| + String value = keyword.syntax;
|
| + return keyword.isPseudo ||
|
| + (identical(value, 'dynamic')) ||
|
| + (identical(value, 'void'));
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + /// Returns true if [token] matches '<' type (',' type)* '>' '(', and
|
| + /// otherwise returns false. The final '(' is not part of the grammar
|
| + /// construct `typeArguments`, but it is required here such that type
|
| + /// arguments in generic method invocations can be recognized, and as few as
|
| + /// possible other constructs will pass (e.g., 'a < C, D > 3').
|
| + bool isValidMethodTypeArguments(Token token) {
|
| + return tryParseMethodTypeArguments(token) != null;
|
| + }
|
| +
|
| + /// Returns token after match if [token] matches '<' type (',' type)* '>' '(',
|
| + /// and otherwise returns null. Does not produce listener events. With respect
|
| + /// to the final '(', please see the description of
|
| + /// [isValidMethodTypeArguments].
|
| + Token tryParseMethodTypeArguments(Token token) {
|
| + if (!identical(token.kind, LT_TOKEN)) return null;
|
| + BeginGroupToken beginToken = token;
|
| + Token endToken = beginToken.endGroup;
|
| + if (endToken == null || !identical(endToken.next.kind, OPEN_PAREN_TOKEN)) {
|
| + return null;
|
| + }
|
| + token = tryParseType(token.next);
|
| + while (token != null && identical(token.kind, COMMA_TOKEN)) {
|
| + token = tryParseType(token.next);
|
| + }
|
| + if (token == null || !identical(token.kind, GT_TOKEN)) return null;
|
| + return token.next;
|
| + }
|
| +
|
| + /// Returns token after match if [token] matches typeName typeArguments?, and
|
| + /// otherwise returns null. Does not produce listener events.
|
| + Token tryParseType(Token token) {
|
| + token = tryParseQualified(token);
|
| + if (token == null) return null;
|
| + Token tokenAfterQualified = token;
|
| + token = tryParseNestedTypeArguments(token);
|
| + return token == null ? tokenAfterQualified : token;
|
| + }
|
| +
|
| + /// Returns token after match if [token] matches identifier ('.' identifier)?,
|
| + /// and otherwise returns null. Does not produce listener events.
|
| + Token tryParseQualified(Token token) {
|
| + if (!identical(token.kind, IDENTIFIER_TOKEN)) return null;
|
| + token = token.next;
|
| + if (!identical(token.kind, PERIOD_TOKEN)) return token;
|
| + token = token.next;
|
| + if (!identical(token.kind, IDENTIFIER_TOKEN)) return null;
|
| + return token.next;
|
| + }
|
| +
|
| + /// Returns token after match if [token] matches '<' type (',' type)* '>',
|
| + /// and otherwise returns null. Does not produce listener events. The final
|
| + /// '>' may be the first character in a '>>' token, in which case a synthetic
|
| + /// '>' token is created and returned, representing the second '>' in the
|
| + /// '>>' token.
|
| + Token tryParseNestedTypeArguments(Token token) {
|
| + if (!identical(token.kind, LT_TOKEN)) return null;
|
| + // If the initial '<' matches the first '>' in a '>>' token, we will have
|
| + // `token.endGroup == null`, so we cannot rely on `token.endGroup == null`
|
| + // to imply that the match must fail. Hence no `token.endGroup == null`
|
| + // test here.
|
| + token = tryParseType(token.next);
|
| + while (token != null && identical(token.kind, COMMA_TOKEN)) {
|
| + token = tryParseType(token.next);
|
| + }
|
| + if (token == null) return null;
|
| + if (identical(token.kind, GT_TOKEN)) return token.next;
|
| + if (!identical(token.kind, GT_GT_TOKEN)) return null;
|
| + // [token] is '>>' of which the final '>' that we are parsing is the first
|
| + // character. In order to keep the parsing process on track we must return
|
| + // a synthetic '>' corresponding to the second character of that '>>'.
|
| + Token syntheticToken = new SymbolToken(GT_INFO, token.charOffset + 1);
|
| + syntheticToken.next = token.next;
|
| + return syntheticToken;
|
| + }
|
| +
|
| + Token parseQualified(Token token) {
|
| + token = parseIdentifier(token);
|
| + while (optional('.', token)) {
|
| + token = parseQualifiedRest(token);
|
| + }
|
| + return token;
|
| + }
|
| +
|
| + Token parseQualifiedRestOpt(Token token) {
|
| + if (optional('.', token)) {
|
| + return parseQualifiedRest(token);
|
| + } else {
|
| + return token;
|
| + }
|
| + }
|
| +
|
| + Token parseQualifiedRest(Token token) {
|
| + assert(optional('.', token));
|
| + Token period = token;
|
| + token = parseIdentifier(token.next);
|
| + listener.handleQualified(period);
|
| + return token;
|
| + }
|
| +
|
| + Token skipBlock(Token token) {
|
| + if (!optional('{', token)) {
|
| + return listener.expectedBlockToSkip(token);
|
| + }
|
| + BeginGroupToken beginGroupToken = token;
|
| + Token endGroup = beginGroupToken.endGroup;
|
| + if (endGroup == null) {
|
| + return listener.unmatched(beginGroupToken);
|
| + } else if (!identical(endGroup.kind, Characters.$CLOSE_CURLY_BRACKET)) {
|
| + return listener.unmatched(beginGroupToken);
|
| + }
|
| + return beginGroupToken.endGroup;
|
| + }
|
| +
|
| + Token parseEnum(Token token) {
|
| + listener.beginEnum(token);
|
| + Token enumKeyword = token;
|
| + token = parseIdentifier(token.next);
|
| + token = expect('{', token);
|
| + int count = 0;
|
| + if (!optional('}', token)) {
|
| + token = parseIdentifier(token);
|
| + count++;
|
| + while (optional(',', token)) {
|
| + token = token.next;
|
| + if (optional('}', token)) break;
|
| + token = parseIdentifier(token);
|
| + count++;
|
| + }
|
| + }
|
| + Token endBrace = token;
|
| + token = expect('}', token);
|
| + listener.endEnum(enumKeyword, endBrace, count);
|
| + return token;
|
| + }
|
| +
|
| + Token parseClassOrNamedMixinApplication(Token token) {
|
| + Token begin = token;
|
| + Token abstractKeyword;
|
| + if (optional('abstract', token)) {
|
| + abstractKeyword = token;
|
| + token = token.next;
|
| + }
|
| + Token classKeyword = token;
|
| + var isMixinApplication = optional('=', peekAfterType(token.next));
|
| + if (isMixinApplication) {
|
| + listener.beginNamedMixinApplication(begin);
|
| + token = parseIdentifier(token.next);
|
| + token = parseTypeVariablesOpt(token);
|
| + token = expect('=', token);
|
| + } else {
|
| + listener.beginClassDeclaration(begin);
|
| + }
|
| +
|
| + // TODO(aprelev@gmail.com): Once 'typedef' named mixin application is
|
| + // removed, move modifiers for named mixin application to the bottom of
|
| + // listener stack. This is so stacks for class declaration and named
|
| + // mixin application look similar.
|
| + int modifierCount = 0;
|
| + if (abstractKeyword != null) {
|
| + parseModifier(abstractKeyword);
|
| + modifierCount++;
|
| + }
|
| + listener.handleModifiers(modifierCount);
|
| +
|
| + if (isMixinApplication) {
|
| + return parseNamedMixinApplication(token, classKeyword);
|
| + } else {
|
| + return parseClass(begin, classKeyword);
|
| + }
|
| + }
|
| +
|
| + Token parseNamedMixinApplication(Token token, Token classKeyword) {
|
| + token = parseMixinApplication(token);
|
| + Token implementsKeyword = null;
|
| + if (optional('implements', token)) {
|
| + implementsKeyword = token;
|
| + token = parseTypeList(token.next);
|
| + }
|
| + listener.endNamedMixinApplication(classKeyword, implementsKeyword, token);
|
| + return expect(';', token);
|
| + }
|
| +
|
| + Token parseClass(Token begin, Token classKeyword) {
|
| + Token token = parseIdentifier(classKeyword.next);
|
| + token = parseTypeVariablesOpt(token);
|
| + Token extendsKeyword;
|
| + if (optional('extends', token)) {
|
| + extendsKeyword = token;
|
| + if (optional('with', peekAfterType(token.next))) {
|
| + token = parseMixinApplication(token.next);
|
| + } else {
|
| + token = parseType(token.next);
|
| + }
|
| + } else {
|
| + extendsKeyword = null;
|
| + listener.handleNoType(token);
|
| + }
|
| + Token implementsKeyword;
|
| + int interfacesCount = 0;
|
| + if (optional('implements', token)) {
|
| + implementsKeyword = token;
|
| + do {
|
| + token = parseType(token.next);
|
| + ++interfacesCount;
|
| + } while (optional(',', token));
|
| + }
|
| + token = parseClassBody(token);
|
| + listener.endClassDeclaration(
|
| + interfacesCount, begin, extendsKeyword, implementsKeyword, token);
|
| + return token.next;
|
| + }
|
| +
|
| + Token parseStringPart(Token token) {
|
| + if (identical(token.kind, STRING_TOKEN)) {
|
| + listener.handleStringPart(token);
|
| + return token.next;
|
| + } else {
|
| + return listener.expected('string', token);
|
| + }
|
| + }
|
| +
|
| + Token parseIdentifier(Token token) {
|
| + if (!token.isIdentifier()) {
|
| + token = listener.expectedIdentifier(token);
|
| + }
|
| + listener.handleIdentifier(token);
|
| + return token.next;
|
| + }
|
| +
|
| + Token expect(String string, Token token) {
|
| + if (!identical(string, token.stringValue)) {
|
| + return listener.expected(string, token);
|
| + }
|
| + return token.next;
|
| + }
|
| +
|
| + Token parseTypeVariable(Token token) {
|
| + listener.beginTypeVariable(token);
|
| + token = parseIdentifier(token);
|
| + Token extendsOrSuper = null;
|
| + if (optional('extends', token) ||
|
| + (enableGenericMethodSyntax && optional('super', token))) {
|
| + extendsOrSuper = token;
|
| + token = parseType(token.next);
|
| + } else {
|
| + listener.handleNoType(token);
|
| + }
|
| + listener.endTypeVariable(token, extendsOrSuper);
|
| + return token;
|
| + }
|
| +
|
| + /**
|
| + * Returns true if the stringValue of the [token] is [value].
|
| + */
|
| + bool optional(String value, Token token) {
|
| + return identical(value, token.stringValue);
|
| + }
|
| +
|
| + /**
|
| + * Returns true if the stringValue of the [token] is either [value1],
|
| + * [value2], or [value3].
|
| + */
|
| + bool isOneOf3(Token token, String value1, String value2, String value3) {
|
| + String stringValue = token.stringValue;
|
| + return value1 == stringValue ||
|
| + value2 == stringValue ||
|
| + value3 == stringValue;
|
| + }
|
| +
|
| + /**
|
| + * Returns true if the stringValue of the [token] is either [value1],
|
| + * [value2], [value3], or [value4].
|
| + */
|
| + bool isOneOf4(
|
| + Token token, String value1, String value2, String value3, String value4) {
|
| + String stringValue = token.stringValue;
|
| + return value1 == stringValue ||
|
| + value2 == stringValue ||
|
| + value3 == stringValue ||
|
| + value4 == stringValue;
|
| + }
|
| +
|
| + bool notEofOrValue(String value, Token token) {
|
| + return !identical(token.kind, EOF_TOKEN) &&
|
| + !identical(value, token.stringValue);
|
| + }
|
| +
|
| + Token parseType(Token token) {
|
| + Token begin = token;
|
| + if (isValidTypeReference(token)) {
|
| + token = parseIdentifier(token);
|
| + token = parseQualifiedRestOpt(token);
|
| + } else {
|
| + token = listener.expectedType(token);
|
| + }
|
| + token = parseTypeArgumentsOpt(token);
|
| + listener.endType(begin, token);
|
| + return token;
|
| + }
|
| +
|
| + Token parseTypeArgumentsOpt(Token token) {
|
| + return parseStuff(
|
| + token,
|
| + (t) => listener.beginTypeArguments(t),
|
| + (t) => parseType(t),
|
| + (c, bt, et) => listener.endTypeArguments(c, bt, et),
|
| + (t) => listener.handleNoTypeArguments(t));
|
| + }
|
| +
|
| + Token parseTypeVariablesOpt(Token token) {
|
| + return parseStuff(
|
| + token,
|
| + (t) => listener.beginTypeVariables(t),
|
| + (t) => parseTypeVariable(t),
|
| + (c, bt, et) => listener.endTypeVariables(c, bt, et),
|
| + (t) => listener.handleNoTypeVariables(t));
|
| + }
|
| +
|
| + // TODO(ahe): Clean this up.
|
| + Token parseStuff(Token token, Function beginStuff, Function stuffParser,
|
| + Function endStuff, Function handleNoStuff) {
|
| + if (optional('<', token)) {
|
| + Token begin = token;
|
| + beginStuff(begin);
|
| + int count = 0;
|
| + do {
|
| + token = stuffParser(token.next);
|
| + ++count;
|
| + } while (optional(',', token));
|
| + Token next = token.next;
|
| + if (identical(token.stringValue, '>>')) {
|
| + token = new SymbolToken(GT_INFO, token.charOffset);
|
| + token.next = new SymbolToken(GT_INFO, token.charOffset + 1);
|
| + token.next.next = next;
|
| + }
|
| + endStuff(count, begin, token);
|
| + return expect('>', token);
|
| + }
|
| + handleNoStuff(token);
|
| + return token;
|
| + }
|
| +
|
| + Token parseTopLevelMember(Token token) {
|
| + Token start = token;
|
| + listener.beginTopLevelMember(token);
|
| +
|
| + Link<Token> identifiers = findMemberName(token);
|
| + if (identifiers.isEmpty) {
|
| + return listener.expectedDeclaration(start);
|
| + }
|
| + Token afterName = identifiers.head;
|
| + identifiers = identifiers.tail;
|
| +
|
| + if (identifiers.isEmpty) {
|
| + return listener.expectedDeclaration(start);
|
| + }
|
| + Token name = identifiers.head;
|
| + identifiers = identifiers.tail;
|
| + Token getOrSet;
|
| + if (!identifiers.isEmpty) {
|
| + String value = identifiers.head.stringValue;
|
| + if ((identical(value, 'get')) || (identical(value, 'set'))) {
|
| + getOrSet = identifiers.head;
|
| + identifiers = identifiers.tail;
|
| + }
|
| + }
|
| + Token type;
|
| + if (!identifiers.isEmpty) {
|
| + if (isValidTypeReference(identifiers.head)) {
|
| + type = identifiers.head;
|
| + identifiers = identifiers.tail;
|
| + }
|
| + }
|
| +
|
| + token = afterName;
|
| + bool isField;
|
| + while (true) {
|
| + // Loop to allow the listener to rewrite the token stream for
|
| + // error handling.
|
| + final String value = token.stringValue;
|
| + if ((identical(value, '(')) ||
|
| + (identical(value, '{')) ||
|
| + (identical(value, '=>'))) {
|
| + isField = false;
|
| + break;
|
| + } else if ((identical(value, '=')) || (identical(value, ','))) {
|
| + isField = true;
|
| + break;
|
| + } else if (identical(value, ';')) {
|
| + if (getOrSet != null) {
|
| + // If we found a "get" keyword, this must be an abstract
|
| + // getter.
|
| + isField = (!identical(getOrSet.stringValue, 'get'));
|
| + // TODO(ahe): This feels like a hack.
|
| + } else {
|
| + isField = true;
|
| + }
|
| + break;
|
| + } else {
|
| + token = listener.unexpected(token);
|
| + if (identical(token.kind, EOF_TOKEN)) return token;
|
| + }
|
| + }
|
| + var modifiers = identifiers.reverse();
|
| + return isField
|
| + ? parseFields(start, modifiers, type, getOrSet, name, true)
|
| + : parseTopLevelMethod(start, modifiers, type, getOrSet, name);
|
| + }
|
| +
|
| + bool isVarFinalOrConst(Token token) {
|
| + String value = token.stringValue;
|
| + return identical('var', value) ||
|
| + identical('final', value) ||
|
| + identical('const', value);
|
| + }
|
| +
|
| + Token expectVarFinalOrConst(
|
| + Link<Token> modifiers, bool hasType, bool allowStatic) {
|
| + int modifierCount = 0;
|
| + Token staticModifier;
|
| + if (allowStatic &&
|
| + !modifiers.isEmpty &&
|
| + optional('static', modifiers.head)) {
|
| + staticModifier = modifiers.head;
|
| + modifierCount++;
|
| + parseModifier(staticModifier);
|
| + modifiers = modifiers.tail;
|
| + }
|
| + if (modifiers.isEmpty) {
|
| + listener.handleModifiers(modifierCount);
|
| + return null;
|
| + }
|
| + if (modifiers.tail.isEmpty) {
|
| + Token modifier = modifiers.head;
|
| + if (isVarFinalOrConst(modifier)) {
|
| + modifierCount++;
|
| + parseModifier(modifier);
|
| + listener.handleModifiers(modifierCount);
|
| + // TODO(ahe): The caller checks for "var Type name", perhaps we should
|
| + // check here instead.
|
| + return modifier;
|
| + }
|
| + }
|
| +
|
| + // Slow case to report errors.
|
| + List<Token> modifierList = modifiers.toList();
|
| + Token varFinalOrConst =
|
| + modifierList.firstWhere(isVarFinalOrConst, orElse: () => null);
|
| + if (allowStatic && staticModifier == null) {
|
| + staticModifier = modifierList.firstWhere(
|
| + (modifier) => optional('static', modifier),
|
| + orElse: () => null);
|
| + if (staticModifier != null) {
|
| + modifierCount++;
|
| + parseModifier(staticModifier);
|
| + modifierList.remove(staticModifier);
|
| + }
|
| + }
|
| + bool hasTypeOrModifier = hasType;
|
| + if (varFinalOrConst != null) {
|
| + parseModifier(varFinalOrConst);
|
| + modifierCount++;
|
| + hasTypeOrModifier = true;
|
| + modifierList.remove(varFinalOrConst);
|
| + }
|
| + listener.handleModifiers(modifierCount);
|
| + var kind = hasTypeOrModifier
|
| + ? MessageKind.EXTRANEOUS_MODIFIER
|
| + : MessageKind.EXTRANEOUS_MODIFIER_REPLACE;
|
| + for (Token modifier in modifierList) {
|
| + listener.reportError(modifier, kind, {'modifier': modifier});
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + Token parseFields(Token start, Link<Token> modifiers, Token type,
|
| + Token getOrSet, Token name, bool isTopLevel) {
|
| + bool hasType = type != null;
|
| + Token varFinalOrConst =
|
| + expectVarFinalOrConst(modifiers, hasType, !isTopLevel);
|
| + bool isVar = false;
|
| + bool hasModifier = false;
|
| + if (varFinalOrConst != null) {
|
| + hasModifier = true;
|
| + isVar = optional('var', varFinalOrConst);
|
| + }
|
| +
|
| + if (getOrSet != null) {
|
| + var kind = (hasModifier || hasType)
|
| + ? MessageKind.EXTRANEOUS_MODIFIER
|
| + : MessageKind.EXTRANEOUS_MODIFIER_REPLACE;
|
| + listener.reportError(getOrSet, kind, {'modifier': getOrSet});
|
| + }
|
| +
|
| + if (!hasType) {
|
| + listener.handleNoType(name);
|
| + } else if (optional('void', type)) {
|
| + listener.handleNoType(name);
|
| + // TODO(ahe): This error is reported twice, second time is from
|
| + // [parseVariablesDeclarationMaybeSemicolon] via
|
| + // [PartialFieldListElement.parseNode].
|
| + listener.reportError(type, MessageKind.VOID_NOT_ALLOWED);
|
| + } else {
|
| + parseType(type);
|
| + if (isVar) {
|
| + listener.reportError(modifiers.head, MessageKind.EXTRANEOUS_MODIFIER,
|
| + {'modifier': modifiers.head});
|
| + }
|
| + }
|
| +
|
| + Token token = parseIdentifier(name);
|
| +
|
| + int fieldCount = 1;
|
| + token = parseVariableInitializerOpt(token);
|
| + while (optional(',', token)) {
|
| + token = parseIdentifier(token.next);
|
| + token = parseVariableInitializerOpt(token);
|
| + ++fieldCount;
|
| + }
|
| + Token semicolon = token;
|
| + token = expectSemicolon(token);
|
| + if (isTopLevel) {
|
| + listener.endTopLevelFields(fieldCount, start, semicolon);
|
| + } else {
|
| + listener.endFields(fieldCount, start, semicolon);
|
| + }
|
| + return token;
|
| + }
|
| +
|
| + Token parseTopLevelMethod(Token start, Link<Token> modifiers, Token type,
|
| + Token getOrSet, Token name) {
|
| + Token externalModifier;
|
| + // TODO(johnniwinther): Move error reporting to resolution to give more
|
| + // specific error messages.
|
| + for (Token modifier in modifiers) {
|
| + if (externalModifier == null && optional('external', modifier)) {
|
| + externalModifier = modifier;
|
| + } else {
|
| + listener.reportError(
|
| + modifier, MessageKind.EXTRANEOUS_MODIFIER, {'modifier': modifier});
|
| + }
|
| + }
|
| + if (externalModifier != null) {
|
| + parseModifier(externalModifier);
|
| + listener.handleModifiers(1);
|
| + } else {
|
| + listener.handleModifiers(0);
|
| + }
|
| +
|
| + if (type == null) {
|
| + listener.handleNoType(name);
|
| + } else {
|
| + parseReturnTypeOpt(type);
|
| + }
|
| + Token token = parseIdentifier(name);
|
| +
|
| + if (enableGenericMethodSyntax && getOrSet == null) {
|
| + token = parseTypeVariablesOpt(token);
|
| + } else {
|
| + listener.handleNoTypeVariables(token);
|
| + }
|
| + token = parseFormalParametersOpt(token);
|
| + bool previousAsyncAwaitKeywordsEnabled = asyncAwaitKeywordsEnabled;
|
| + token = parseAsyncModifier(token);
|
| + token = parseFunctionBody(token, false, externalModifier != null);
|
| + asyncAwaitKeywordsEnabled = previousAsyncAwaitKeywordsEnabled;
|
| + Token endToken = token;
|
| + token = token.next;
|
| + if (token.kind == BAD_INPUT_TOKEN) {
|
| + token = listener.unexpected(token);
|
| + }
|
| + listener.endTopLevelMethod(start, getOrSet, endToken);
|
| + return token;
|
| + }
|
| +
|
| + /// Looks ahead to find the name of a member. Returns a link of the modifiers,
|
| + /// set/get, (operator) name, and either the start of the method body or the
|
| + /// end of the declaration.
|
| + ///
|
| + /// Examples:
|
| + ///
|
| + /// int get foo;
|
| + /// results in
|
| + /// [';', 'foo', 'get', 'int']
|
| + ///
|
| + ///
|
| + /// static const List<int> foo = null;
|
| + /// results in
|
| + /// ['=', 'foo', 'List', 'const', 'static']
|
| + ///
|
| + ///
|
| + /// get foo async* { return null }
|
| + /// results in
|
| + /// ['{', 'foo', 'get']
|
| + ///
|
| + ///
|
| + /// operator *(arg) => null;
|
| + /// results in
|
| + /// ['(', '*', 'operator']
|
| + ///
|
| + Link<Token> findMemberName(Token token) {
|
| + Link<Token> identifiers = const Link<Token>();
|
| +
|
| + // `true` if 'get' has been seen.
|
| + bool isGetter = false;
|
| + // `true` if an identifier has been seen after 'get'.
|
| + bool hasName = false;
|
| +
|
| + while (token.kind != EOF_TOKEN) {
|
| + String value = token.stringValue;
|
| + if (value == 'get') {
|
| + isGetter = true;
|
| + } else if (hasName && (value == 'sync' || value == 'async')) {
|
| + // Skip.
|
| + token = token.next;
|
| + value = token.stringValue;
|
| + if (value == '*') {
|
| + // Skip.
|
| + token = token.next;
|
| + }
|
| + continue;
|
| + } else if (value == '(' || value == '{' || value == '=>') {
|
| + // A method.
|
| + identifiers = identifiers.prepend(token);
|
| + return identifiers;
|
| + } else if (value == '=' || value == ';' || value == ',') {
|
| + // A field or abstract getter.
|
| + identifiers = identifiers.prepend(token);
|
| + return identifiers;
|
| + } else if (isGetter) {
|
| + hasName = true;
|
| + }
|
| + identifiers = identifiers.prepend(token);
|
| + if (isValidTypeReference(token)) {
|
| + // type ...
|
| + if (optional('.', token.next)) {
|
| + // type '.' ...
|
| + if (token.next.next.isIdentifier()) {
|
| + // type '.' identifier
|
| + token = token.next.next;
|
| + }
|
| + }
|
| + if (optional('<', token.next)) {
|
| + if (token.next is BeginGroupToken) {
|
| + BeginGroupToken beginGroup = token.next;
|
| + if (beginGroup.endGroup == null) {
|
| + listener.unmatched(beginGroup);
|
| + }
|
| + token = beginGroup.endGroup;
|
| + }
|
| + }
|
| + }
|
| + token = token.next;
|
| + }
|
| + return const Link<Token>();
|
| + }
|
| +
|
| + Token parseVariableInitializerOpt(Token token) {
|
| + if (optional('=', token)) {
|
| + Token assignment = token;
|
| + listener.beginInitializer(token);
|
| + token = parseExpression(token.next);
|
| + listener.endInitializer(assignment);
|
| + }
|
| + return token;
|
| + }
|
| +
|
| + Token parseInitializersOpt(Token token) {
|
| + if (optional(':', token)) {
|
| + return parseInitializers(token);
|
| + } else {
|
| + listener.handleNoInitializers();
|
| + return token;
|
| + }
|
| + }
|
| +
|
| + Token parseInitializers(Token token) {
|
| + Token begin = token;
|
| + listener.beginInitializers(begin);
|
| + expect(':', token);
|
| + int count = 0;
|
| + bool old = mayParseFunctionExpressions;
|
| + mayParseFunctionExpressions = false;
|
| + do {
|
| + token = parseExpression(token.next);
|
| + ++count;
|
| + } while (optional(',', token));
|
| + mayParseFunctionExpressions = old;
|
| + listener.endInitializers(count, begin, token);
|
| + return token;
|
| + }
|
| +
|
| + Token parseLiteralStringOrRecoverExpression(Token token) {
|
| + if (identical(token.kind, STRING_TOKEN)) {
|
| + return parseLiteralString(token);
|
| + } else {
|
| + listener.recoverableError(token, "unexpected");
|
| + return parseExpression(token);
|
| + }
|
| + }
|
| +
|
| + Token expectSemicolon(Token token) {
|
| + return expect(';', token);
|
| + }
|
| +
|
| + bool isModifier(Token token) {
|
| + final String value = token.stringValue;
|
| + return (identical('final', value)) ||
|
| + (identical('var', value)) ||
|
| + (identical('const', value)) ||
|
| + (identical('abstract', value)) ||
|
| + (identical('static', value)) ||
|
| + (identical('external', value));
|
| + }
|
| +
|
| + Token parseModifier(Token token) {
|
| + assert(isModifier(token));
|
| + listener.handleModifier(token);
|
| + return token.next;
|
| + }
|
| +
|
| + void parseModifierList(Link<Token> tokens) {
|
| + int count = 0;
|
| + for (; !tokens.isEmpty; tokens = tokens.tail) {
|
| + Token token = tokens.head;
|
| + if (isModifier(token)) {
|
| + parseModifier(token);
|
| + } else {
|
| + listener.unexpected(token);
|
| + // Skip the remaining modifiers.
|
| + break;
|
| + }
|
| + count++;
|
| + }
|
| + listener.handleModifiers(count);
|
| + }
|
| +
|
| + Token parseModifiers(Token token) {
|
| + int count = 0;
|
| + while (identical(token.kind, KEYWORD_TOKEN)) {
|
| + if (!isModifier(token)) break;
|
| + token = parseModifier(token);
|
| + count++;
|
| + }
|
| + listener.handleModifiers(count);
|
| + return token;
|
| + }
|
| +
|
| + /**
|
| + * Returns the first token after the type starting at [token].
|
| + * This method assumes that [token] is an identifier (or void).
|
| + * Use [peekAfterIfType] if [token] isn't known to be an identifier.
|
| + */
|
| + Token peekAfterType(Token token) {
|
| + // We are looking at "identifier ...".
|
| + Token peek = token.next;
|
| + if (identical(peek.kind, PERIOD_TOKEN)) {
|
| + if (peek.next.isIdentifier()) {
|
| + // Look past a library prefix.
|
| + peek = peek.next.next;
|
| + }
|
| + }
|
| + // We are looking at "qualified ...".
|
| + if (identical(peek.kind, LT_TOKEN)) {
|
| + // Possibly generic type.
|
| + // We are looking at "qualified '<'".
|
| + BeginGroupToken beginGroupToken = peek;
|
| + Token gtToken = beginGroupToken.endGroup;
|
| + if (gtToken != null) {
|
| + // We are looking at "qualified '<' ... '>' ...".
|
| + return gtToken.next;
|
| + }
|
| + }
|
| + return peek;
|
| + }
|
| +
|
| + /**
|
| + * If [token] is the start of a type, returns the token after that type.
|
| + * If [token] is not the start of a type, null is returned.
|
| + */
|
| + Token peekAfterIfType(Token token) {
|
| + if (!optional('void', token) && !token.isIdentifier()) {
|
| + return null;
|
| + }
|
| + return peekAfterType(token);
|
| + }
|
| +
|
| + Token parseClassBody(Token token) {
|
| + Token begin = token;
|
| + listener.beginClassBody(token);
|
| + if (!optional('{', token)) {
|
| + token = listener.expectedClassBody(token);
|
| + }
|
| + token = token.next;
|
| + int count = 0;
|
| + while (notEofOrValue('}', token)) {
|
| + token = parseMember(token);
|
| + ++count;
|
| + }
|
| + expect('}', token);
|
| + listener.endClassBody(count, begin, token);
|
| + return token;
|
| + }
|
| +
|
| + bool isGetOrSet(Token token) {
|
| + final String value = token.stringValue;
|
| + return (identical(value, 'get')) || (identical(value, 'set'));
|
| + }
|
| +
|
| + bool isFactoryDeclaration(Token token) {
|
| + if (optional('external', token)) token = token.next;
|
| + if (optional('const', token)) token = token.next;
|
| + return optional('factory', token);
|
| + }
|
| +
|
| + Token parseMember(Token token) {
|
| + token = parseMetadataStar(token);
|
| + Token start = token;
|
| + listener.beginMember(token);
|
| + if (isFactoryDeclaration(token)) {
|
| + token = parseFactoryMethod(token);
|
| + listener.endMember();
|
| + assert (token != null);
|
| + return token;
|
| + }
|
| +
|
| + Link<Token> identifiers = findMemberName(token);
|
| + if (identifiers.isEmpty) {
|
| + return listener.expectedDeclaration(start);
|
| + }
|
| + Token afterName = identifiers.head;
|
| + identifiers = identifiers.tail;
|
| +
|
| + if (identifiers.isEmpty) {
|
| + return listener.expectedDeclaration(start);
|
| + }
|
| + Token name = identifiers.head;
|
| + identifiers = identifiers.tail;
|
| + if (!identifiers.isEmpty) {
|
| + if (optional('operator', identifiers.head)) {
|
| + name = identifiers.head;
|
| + identifiers = identifiers.tail;
|
| + }
|
| + }
|
| + Token getOrSet;
|
| + if (!identifiers.isEmpty) {
|
| + if (isGetOrSet(identifiers.head)) {
|
| + getOrSet = identifiers.head;
|
| + identifiers = identifiers.tail;
|
| + }
|
| + }
|
| + Token type;
|
| + if (!identifiers.isEmpty) {
|
| + if (isValidTypeReference(identifiers.head)) {
|
| + type = identifiers.head;
|
| + identifiers = identifiers.tail;
|
| + }
|
| + }
|
| +
|
| + token = afterName;
|
| + bool isField;
|
| + while (true) {
|
| + // Loop to allow the listener to rewrite the token stream for
|
| + // error handling.
|
| + final String value = token.stringValue;
|
| + if ((identical(value, '(')) ||
|
| + (identical(value, '.')) ||
|
| + (identical(value, '{')) ||
|
| + (identical(value, '=>')) ||
|
| + (enableGenericMethodSyntax && identical(value, '<'))) {
|
| + isField = false;
|
| + break;
|
| + } else if (identical(value, ';')) {
|
| + if (getOrSet != null) {
|
| + // If we found a "get" keyword, this must be an abstract
|
| + // getter.
|
| + isField = (!identical(getOrSet.stringValue, 'get'));
|
| + // TODO(ahe): This feels like a hack.
|
| + } else {
|
| + isField = true;
|
| + }
|
| + break;
|
| + } else if ((identical(value, '=')) || (identical(value, ','))) {
|
| + isField = true;
|
| + break;
|
| + } else {
|
| + token = listener.unexpected(token);
|
| + if (identical(token.kind, EOF_TOKEN)) {
|
| + // TODO(ahe): This is a hack, see parseTopLevelMember.
|
| + listener.endFields(1, start, token);
|
| + listener.endMember();
|
| + return token;
|
| + }
|
| + }
|
| + }
|
| +
|
| + var modifiers = identifiers.reverse();
|
| + token = isField
|
| + ? parseFields(start, modifiers, type, getOrSet, name, false)
|
| + : parseMethod(start, modifiers, type, getOrSet, name);
|
| + listener.endMember();
|
| + return token;
|
| + }
|
| +
|
| + Token parseMethod(Token start, Link<Token> modifiers, Token type,
|
| + Token getOrSet, Token name) {
|
| + Token externalModifier;
|
| + Token staticModifier;
|
| + Token constModifier;
|
| + int modifierCount = 0;
|
| + int allowedModifierCount = 1;
|
| + // TODO(johnniwinther): Move error reporting to resolution to give more
|
| + // specific error messages.
|
| + for (Token modifier in modifiers) {
|
| + if (externalModifier == null && optional('external', modifier)) {
|
| + modifierCount++;
|
| + externalModifier = modifier;
|
| + if (modifierCount != allowedModifierCount) {
|
| + listener.reportError(modifier, MessageKind.EXTRANEOUS_MODIFIER,
|
| + {'modifier': modifier});
|
| + }
|
| + allowedModifierCount++;
|
| + } else if (staticModifier == null && optional('static', modifier)) {
|
| + modifierCount++;
|
| + staticModifier = modifier;
|
| + if (modifierCount != allowedModifierCount) {
|
| + listener.reportError(modifier, MessageKind.EXTRANEOUS_MODIFIER,
|
| + {'modifier': modifier});
|
| + }
|
| + } else if (constModifier == null && optional('const', modifier)) {
|
| + modifierCount++;
|
| + constModifier = modifier;
|
| + if (modifierCount != allowedModifierCount) {
|
| + listener.reportError(modifier, MessageKind.EXTRANEOUS_MODIFIER,
|
| + {'modifier': modifier});
|
| + }
|
| + } else {
|
| + listener.reportError(
|
| + modifier, MessageKind.EXTRANEOUS_MODIFIER, {'modifier': modifier});
|
| + }
|
| + }
|
| + if (getOrSet != null && constModifier != null) {
|
| + listener.reportError(constModifier, MessageKind.EXTRANEOUS_MODIFIER,
|
| + {'modifier': constModifier});
|
| + }
|
| + parseModifierList(modifiers);
|
| +
|
| + if (type == null) {
|
| + listener.handleNoType(name);
|
| + } else {
|
| + parseReturnTypeOpt(type);
|
| + }
|
| + Token token;
|
| + if (optional('operator', name)) {
|
| + token = parseOperatorName(name);
|
| + if (staticModifier != null) {
|
| + listener.reportError(staticModifier, MessageKind.EXTRANEOUS_MODIFIER,
|
| + {'modifier': staticModifier});
|
| + }
|
| + } else {
|
| + token = parseIdentifier(name);
|
| + }
|
| +
|
| + token = parseQualifiedRestOpt(token);
|
| + if (enableGenericMethodSyntax && getOrSet == null) {
|
| + token = parseTypeVariablesOpt(token);
|
| + } else {
|
| + listener.handleNoTypeVariables(token);
|
| + }
|
| + token = parseFormalParametersOpt(token);
|
| + token = parseInitializersOpt(token);
|
| + bool previousAsyncAwaitKeywordsEnabled = asyncAwaitKeywordsEnabled;
|
| + token = parseAsyncModifier(token);
|
| + if (optional('=', token)) {
|
| + token = parseRedirectingFactoryBody(token);
|
| + } else {
|
| + token = parseFunctionBody(
|
| + token, false, staticModifier == null || externalModifier != null);
|
| + }
|
| + asyncAwaitKeywordsEnabled = previousAsyncAwaitKeywordsEnabled;
|
| + listener.endMethod(getOrSet, start, token);
|
| + return token.next;
|
| + }
|
| +
|
| + Token parseFactoryMethod(Token token) {
|
| + assert(isFactoryDeclaration(token));
|
| + Token start = token;
|
| + Token externalModifier;
|
| + if (identical(token.stringValue, 'external')) {
|
| + externalModifier = token;
|
| + token = token.next;
|
| + }
|
| + if (optional('const', token)) {
|
| + token = token.next; // Skip const.
|
| + }
|
| + Token factoryKeyword = token;
|
| + listener.beginFactoryMethod(factoryKeyword);
|
| + token = token.next; // Skip 'factory'.
|
| + token = parseConstructorReference(token);
|
| + token = parseFormalParameters(token);
|
| + token = parseAsyncModifier(token);
|
| + if (optional('=', token)) {
|
| + token = parseRedirectingFactoryBody(token);
|
| + } else {
|
| + token = parseFunctionBody(token, false, externalModifier != null);
|
| + }
|
| + listener.endFactoryMethod(start, token);
|
| + return token.next;
|
| + }
|
| +
|
| + Token parseOperatorName(Token token) {
|
| + assert(optional('operator', token));
|
| + if (isUserDefinableOperator(token.next.stringValue)) {
|
| + Token operator = token;
|
| + token = token.next;
|
| + listener.handleOperatorName(operator, token);
|
| + return token.next;
|
| + } else {
|
| + return parseIdentifier(token);
|
| + }
|
| + }
|
| +
|
| + Token parseFunction(Token token, Token getOrSet) {
|
| + listener.beginFunction(token);
|
| + token = parseModifiers(token);
|
| + if (identical(getOrSet, token)) {
|
| + // get <name> => ...
|
| + token = token.next;
|
| + listener.handleNoType(token);
|
| + listener.beginFunctionName(token);
|
| + if (optional('operator', token)) {
|
| + token = parseOperatorName(token);
|
| + } else {
|
| + token = parseIdentifier(token);
|
| + }
|
| + } else if (optional('operator', token)) {
|
| + // operator <op> (...
|
| + listener.handleNoType(token);
|
| + listener.beginFunctionName(token);
|
| + token = parseOperatorName(token);
|
| + } else {
|
| + // <type>? <get>? <name>
|
| + token = parseReturnTypeOpt(token);
|
| + if (identical(getOrSet, token)) {
|
| + token = token.next;
|
| + }
|
| + listener.beginFunctionName(token);
|
| + if (optional('operator', token)) {
|
| + token = parseOperatorName(token);
|
| + } else {
|
| + token = parseIdentifier(token);
|
| + }
|
| + }
|
| + token = parseQualifiedRestOpt(token);
|
| + listener.endFunctionName(token);
|
| + if (enableGenericMethodSyntax && getOrSet == null) {
|
| + token = parseTypeVariablesOpt(token);
|
| + } else {
|
| + listener.handleNoTypeVariables(token);
|
| + }
|
| + token = parseFormalParametersOpt(token);
|
| + token = parseInitializersOpt(token);
|
| + bool previousAsyncAwaitKeywordsEnabled = asyncAwaitKeywordsEnabled;
|
| + token = parseAsyncModifier(token);
|
| + if (optional('=', token)) {
|
| + token = parseRedirectingFactoryBody(token);
|
| + } else {
|
| + token = parseFunctionBody(token, false, true);
|
| + }
|
| + asyncAwaitKeywordsEnabled = previousAsyncAwaitKeywordsEnabled;
|
| + listener.endFunction(getOrSet, token);
|
| + return token.next;
|
| + }
|
| +
|
| + Token parseUnnamedFunction(Token token) {
|
| + listener.beginUnnamedFunction(token);
|
| + token = parseFormalParameters(token);
|
| + bool previousAsyncAwaitKeywordsEnabled = asyncAwaitKeywordsEnabled;
|
| + token = parseAsyncModifier(token);
|
| + bool isBlock = optional('{', token);
|
| + token = parseFunctionBody(token, true, false);
|
| + asyncAwaitKeywordsEnabled = previousAsyncAwaitKeywordsEnabled;
|
| + listener.endUnnamedFunction(token);
|
| + return isBlock ? token.next : token;
|
| + }
|
| +
|
| + Token parseFunctionDeclaration(Token token) {
|
| + listener.beginFunctionDeclaration(token);
|
| + token = parseFunction(token, null);
|
| + listener.endFunctionDeclaration(token);
|
| + return token;
|
| + }
|
| +
|
| + Token parseFunctionExpression(Token token) {
|
| + listener.beginFunction(token);
|
| + listener.handleModifiers(0);
|
| + token = parseReturnTypeOpt(token);
|
| + listener.beginFunctionName(token);
|
| + token = parseIdentifier(token);
|
| + listener.endFunctionName(token);
|
| + if (enableGenericMethodSyntax) {
|
| + token = parseTypeVariablesOpt(token);
|
| + } else {
|
| + listener.handleNoTypeVariables(token);
|
| + }
|
| + token = parseFormalParameters(token);
|
| + listener.handleNoInitializers();
|
| + bool previousAsyncAwaitKeywordsEnabled = asyncAwaitKeywordsEnabled;
|
| + token = parseAsyncModifier(token);
|
| + bool isBlock = optional('{', token);
|
| + token = parseFunctionBody(token, true, false);
|
| + asyncAwaitKeywordsEnabled = previousAsyncAwaitKeywordsEnabled;
|
| + listener.endFunction(null, token);
|
| + return isBlock ? token.next : token;
|
| + }
|
| +
|
| + Token parseConstructorReference(Token token) {
|
| + Token start = token;
|
| + listener.beginConstructorReference(start);
|
| + token = parseIdentifier(token);
|
| + token = parseQualifiedRestOpt(token);
|
| + token = parseTypeArgumentsOpt(token);
|
| + Token period = null;
|
| + if (optional('.', token)) {
|
| + period = token;
|
| + token = parseIdentifier(token.next);
|
| + }
|
| + listener.endConstructorReference(start, period, token);
|
| + return token;
|
| + }
|
| +
|
| + Token parseRedirectingFactoryBody(Token token) {
|
| + listener.beginRedirectingFactoryBody(token);
|
| + assert(optional('=', token));
|
| + Token equals = token;
|
| + token = parseConstructorReference(token.next);
|
| + Token semicolon = token;
|
| + expectSemicolon(token);
|
| + listener.endRedirectingFactoryBody(equals, semicolon);
|
| + return token;
|
| + }
|
| +
|
| + Token parseFunctionBody(Token token, bool isExpression, bool allowAbstract) {
|
| + if (optional(';', token)) {
|
| + if (!allowAbstract) {
|
| + listener.reportError(token, MessageKind.BODY_EXPECTED);
|
| + }
|
| + listener.endFunctionBody(0, null, token);
|
| + return token;
|
| + } else if (optional('=>', token)) {
|
| + Token begin = token;
|
| + token = parseExpression(token.next);
|
| + if (!isExpression) {
|
| + expectSemicolon(token);
|
| + listener.endReturnStatement(true, begin, token);
|
| + } else {
|
| + listener.endReturnStatement(true, begin, null);
|
| + }
|
| + return token;
|
| + }
|
| + Token begin = token;
|
| + int statementCount = 0;
|
| + if (!optional('{', token)) {
|
| + return listener.expectedFunctionBody(token);
|
| + }
|
| +
|
| + listener.beginFunctionBody(begin);
|
| + token = token.next;
|
| + while (notEofOrValue('}', token)) {
|
| + token = parseStatement(token);
|
| + ++statementCount;
|
| + }
|
| + listener.endFunctionBody(statementCount, begin, token);
|
| + expect('}', token);
|
| + return token;
|
| + }
|
| +
|
| + Token parseAsyncModifier(Token token) {
|
| + Token async;
|
| + Token star;
|
| + asyncAwaitKeywordsEnabled = false;
|
| + if (optional('async', token)) {
|
| + asyncAwaitKeywordsEnabled = true;
|
| + async = token;
|
| + token = token.next;
|
| + if (optional('*', token)) {
|
| + star = token;
|
| + token = token.next;
|
| + }
|
| + } else if (optional('sync', token)) {
|
| + async = token;
|
| + token = token.next;
|
| + if (optional('*', token)) {
|
| + asyncAwaitKeywordsEnabled = true;
|
| + star = token;
|
| + token = token.next;
|
| + } else {
|
| + listener.reportError(async, MessageKind.INVALID_SYNC_MODIFIER);
|
| + }
|
| + }
|
| + listener.handleAsyncModifier(async, star);
|
| + return token;
|
| + }
|
| +
|
| + Token parseStatement(Token token) {
|
| + final value = token.stringValue;
|
| + if (identical(token.kind, IDENTIFIER_TOKEN)) {
|
| + return parseExpressionStatementOrDeclaration(token);
|
| + } else if (identical(value, '{')) {
|
| + return parseBlock(token);
|
| + } else if (identical(value, 'return')) {
|
| + return parseReturnStatement(token);
|
| + } else if (identical(value, 'var') || identical(value, 'final')) {
|
| + return parseVariablesDeclaration(token);
|
| + } else if (identical(value, 'if')) {
|
| + return parseIfStatement(token);
|
| + } else if (asyncAwaitKeywordsEnabled && identical(value, 'await')) {
|
| + if (identical(token.next.stringValue, 'for')) {
|
| + return parseForStatement(token, token.next);
|
| + } else {
|
| + return parseExpressionStatement(token);
|
| + }
|
| + } else if (identical(value, 'for')) {
|
| + return parseForStatement(null, token);
|
| + } else if (identical(value, 'rethrow')) {
|
| + return parseRethrowStatement(token);
|
| + } else if (identical(value, 'throw') && optional(';', token.next)) {
|
| + // TODO(kasperl): Stop dealing with throw here.
|
| + return parseRethrowStatement(token);
|
| + } else if (identical(value, 'void')) {
|
| + return parseExpressionStatementOrDeclaration(token);
|
| + } else if (identical(value, 'while')) {
|
| + return parseWhileStatement(token);
|
| + } else if (identical(value, 'do')) {
|
| + return parseDoWhileStatement(token);
|
| + } else if (identical(value, 'try')) {
|
| + return parseTryStatement(token);
|
| + } else if (identical(value, 'switch')) {
|
| + return parseSwitchStatement(token);
|
| + } else if (identical(value, 'break')) {
|
| + return parseBreakStatement(token);
|
| + } else if (identical(value, 'continue')) {
|
| + return parseContinueStatement(token);
|
| + } else if (identical(value, 'assert')) {
|
| + return parseAssertStatement(token);
|
| + } else if (identical(value, ';')) {
|
| + return parseEmptyStatement(token);
|
| + } else if (asyncAwaitKeywordsEnabled && identical(value, 'yield')) {
|
| + return parseYieldStatement(token);
|
| + } else if (identical(value, 'const')) {
|
| + return parseExpressionStatementOrConstDeclaration(token);
|
| + } else if (token.isIdentifier()) {
|
| + return parseExpressionStatementOrDeclaration(token);
|
| + } else {
|
| + return parseExpressionStatement(token);
|
| + }
|
| + }
|
| +
|
| + Token parseYieldStatement(Token token) {
|
| + Token begin = token;
|
| + listener.beginYieldStatement(begin);
|
| + assert(identical('yield', token.stringValue));
|
| + token = token.next;
|
| + Token starToken;
|
| + if (optional('*', token)) {
|
| + starToken = token;
|
| + token = token.next;
|
| + }
|
| + token = parseExpression(token);
|
| + listener.endYieldStatement(begin, starToken, token);
|
| + return expectSemicolon(token);
|
| + }
|
| +
|
| + Token parseReturnStatement(Token token) {
|
| + Token begin = token;
|
| + listener.beginReturnStatement(begin);
|
| + assert(identical('return', token.stringValue));
|
| + token = token.next;
|
| + if (optional(';', token)) {
|
| + listener.endReturnStatement(false, begin, token);
|
| + } else {
|
| + token = parseExpression(token);
|
| + listener.endReturnStatement(true, begin, token);
|
| + }
|
| + return expectSemicolon(token);
|
| + }
|
| +
|
| + Token peekIdentifierAfterType(Token token) {
|
| + Token peek = peekAfterType(token);
|
| + if (peek != null && peek.isIdentifier()) {
|
| + // We are looking at "type identifier".
|
| + return peek;
|
| + } else {
|
| + return null;
|
| + }
|
| + }
|
| +
|
| + Token peekIdentifierAfterOptionalType(Token token) {
|
| + Token peek = peekAfterIfType(token);
|
| + if (peek != null && peek.isIdentifier()) {
|
| + // We are looking at "type identifier".
|
| + return peek;
|
| + } else if (token.isIdentifier()) {
|
| + // We are looking at "identifier".
|
| + return token;
|
| + } else {
|
| + return null;
|
| + }
|
| + }
|
| +
|
| + Token parseExpressionStatementOrDeclaration(Token token) {
|
| + assert(token.isIdentifier() || identical(token.stringValue, 'void'));
|
| + Token identifier = peekIdentifierAfterType(token);
|
| + if (identifier != null) {
|
| + assert(identifier.isIdentifier());
|
| + Token afterId = identifier.next;
|
| + int afterIdKind = afterId.kind;
|
| + if (identical(afterIdKind, EQ_TOKEN) ||
|
| + identical(afterIdKind, SEMICOLON_TOKEN) ||
|
| + identical(afterIdKind, COMMA_TOKEN)) {
|
| + // We are looking at "type identifier" followed by '=', ';', ','.
|
| + return parseVariablesDeclaration(token);
|
| + } else if (identical(afterIdKind, OPEN_PAREN_TOKEN)) {
|
| + // We are looking at "type identifier '('".
|
| + BeginGroupToken beginParen = afterId;
|
| + Token endParen = beginParen.endGroup;
|
| + // TODO(eernst): Check for NPE as described in issue 26252.
|
| + Token afterParens = endParen.next;
|
| + if (optional('{', afterParens) ||
|
| + optional('=>', afterParens) ||
|
| + optional('async', afterParens) ||
|
| + optional('sync', afterParens)) {
|
| + // We are looking at "type identifier '(' ... ')'" followed
|
| + // by '{', '=>', 'async', or 'sync'.
|
| + return parseFunctionDeclaration(token);
|
| + }
|
| + } else if (enableGenericMethodSyntax &&
|
| + identical(afterIdKind, LT_TOKEN)) {
|
| + // We are looking at "type identifier '<'".
|
| + BeginGroupToken beginAngle = afterId;
|
| + Token endAngle = beginAngle.endGroup;
|
| + if (endAngle != null &&
|
| + identical(endAngle.next.kind, OPEN_PAREN_TOKEN)) {
|
| + BeginGroupToken beginParen = endAngle.next;
|
| + Token endParen = beginParen.endGroup;
|
| + if (endParen != null) {
|
| + Token afterParens = endParen.next;
|
| + if (optional('{', afterParens) ||
|
| + optional('=>', afterParens) ||
|
| + optional('async', afterParens) ||
|
| + optional('sync', afterParens)) {
|
| + // We are looking at "type identifier '<' ... '>' '(' ... ')'"
|
| + // followed by '{', '=>', 'async', or 'sync'.
|
| + return parseFunctionDeclaration(token);
|
| + }
|
| + }
|
| + }
|
| + }
|
| + // Fall-through to expression statement.
|
| + } else {
|
| + if (optional(':', token.next)) {
|
| + return parseLabeledStatement(token);
|
| + } else if (optional('(', token.next)) {
|
| + BeginGroupToken begin = token.next;
|
| + // TODO(eernst): Check for NPE as described in issue 26252.
|
| + String afterParens = begin.endGroup.next.stringValue;
|
| + if (identical(afterParens, '{') ||
|
| + identical(afterParens, '=>') ||
|
| + identical(afterParens, 'async') ||
|
| + identical(afterParens, 'sync')) {
|
| + return parseFunctionDeclaration(token);
|
| + }
|
| + } else if (enableGenericMethodSyntax && optional('<', token.next)) {
|
| + BeginGroupToken beginAngle = token.next;
|
| + Token endAngle = beginAngle.endGroup;
|
| + if (endAngle != null &&
|
| + identical(endAngle.next.kind, OPEN_PAREN_TOKEN)) {
|
| + BeginGroupToken beginParen = endAngle.next;
|
| + Token endParen = beginParen.endGroup;
|
| + if (endParen != null) {
|
| + String afterParens = endParen.next.stringValue;
|
| + if (identical(afterParens, '{') ||
|
| + identical(afterParens, '=>') ||
|
| + identical(afterParens, 'async') ||
|
| + identical(afterParens, 'sync')) {
|
| + return parseFunctionDeclaration(token);
|
| + }
|
| + }
|
| + }
|
| + // Fall through to expression statement.
|
| + }
|
| + }
|
| + return parseExpressionStatement(token);
|
| + }
|
| +
|
| + Token parseExpressionStatementOrConstDeclaration(Token token) {
|
| + assert(identical(token.stringValue, 'const'));
|
| + if (isModifier(token.next)) {
|
| + return parseVariablesDeclaration(token);
|
| + }
|
| + Token identifier = peekIdentifierAfterOptionalType(token.next);
|
| + if (identifier != null) {
|
| + assert(identifier.isIdentifier());
|
| + Token afterId = identifier.next;
|
| + int afterIdKind = afterId.kind;
|
| + if (identical(afterIdKind, EQ_TOKEN) ||
|
| + identical(afterIdKind, SEMICOLON_TOKEN) ||
|
| + identical(afterIdKind, COMMA_TOKEN)) {
|
| + // We are looking at "const type identifier" followed by '=', ';', or
|
| + // ','.
|
| + return parseVariablesDeclaration(token);
|
| + }
|
| + // Fall-through to expression statement.
|
| + }
|
| +
|
| + return parseExpressionStatement(token);
|
| + }
|
| +
|
| + Token parseLabel(Token token) {
|
| + token = parseIdentifier(token);
|
| + Token colon = token;
|
| + token = expect(':', token);
|
| + listener.handleLabel(colon);
|
| + return token;
|
| + }
|
| +
|
| + Token parseLabeledStatement(Token token) {
|
| + int labelCount = 0;
|
| + do {
|
| + token = parseLabel(token);
|
| + labelCount++;
|
| + } while (token.isIdentifier() && optional(':', token.next));
|
| + listener.beginLabeledStatement(token, labelCount);
|
| + token = parseStatement(token);
|
| + listener.endLabeledStatement(labelCount);
|
| + return token;
|
| + }
|
| +
|
| + Token parseExpressionStatement(Token token) {
|
| + listener.beginExpressionStatement(token);
|
| + token = parseExpression(token);
|
| + listener.endExpressionStatement(token);
|
| + return expectSemicolon(token);
|
| + }
|
| +
|
| + Token parseExpression(Token token) {
|
| + return optional('throw', token)
|
| + ? parseThrowExpression(token, true)
|
| + : parsePrecedenceExpression(token, ASSIGNMENT_PRECEDENCE, true);
|
| + }
|
| +
|
| + Token parseExpressionWithoutCascade(Token token) {
|
| + return optional('throw', token)
|
| + ? parseThrowExpression(token, false)
|
| + : parsePrecedenceExpression(token, ASSIGNMENT_PRECEDENCE, false);
|
| + }
|
| +
|
| + Token parseConditionalExpressionRest(Token token) {
|
| + assert(optional('?', token));
|
| + Token question = token;
|
| + token = parseExpressionWithoutCascade(token.next);
|
| + Token colon = token;
|
| + token = expect(':', token);
|
| + token = parseExpressionWithoutCascade(token);
|
| + listener.handleConditionalExpression(question, colon);
|
| + return token;
|
| + }
|
| +
|
| + Token parsePrecedenceExpression(
|
| + Token token, int precedence, bool allowCascades) {
|
| + assert(precedence >= 1);
|
| + assert(precedence <= POSTFIX_PRECEDENCE);
|
| + token = parseUnaryExpression(token, allowCascades);
|
| + PrecedenceInfo info = token.info;
|
| + int tokenLevel = info.precedence;
|
| + for (int level = tokenLevel; level >= precedence; --level) {
|
| + while (identical(tokenLevel, level)) {
|
| + Token operator = token;
|
| + if (identical(tokenLevel, CASCADE_PRECEDENCE)) {
|
| + if (!allowCascades) {
|
| + return token;
|
| + }
|
| + token = parseCascadeExpression(token);
|
| + } else if (identical(tokenLevel, ASSIGNMENT_PRECEDENCE)) {
|
| + // Right associative, so we recurse at the same precedence
|
| + // level.
|
| + token = parsePrecedenceExpression(token.next, level, allowCascades);
|
| + listener.handleAssignmentExpression(operator);
|
| + } else if (identical(tokenLevel, POSTFIX_PRECEDENCE)) {
|
| + if (identical(info, PERIOD_INFO) ||
|
| + identical(info, QUESTION_PERIOD_INFO)) {
|
| + // Left associative, so we recurse at the next higher
|
| + // precedence level. However, POSTFIX_PRECEDENCE is the
|
| + // highest level, so we just call parseUnaryExpression
|
| + // directly.
|
| + token = parseUnaryExpression(token.next, allowCascades);
|
| + listener.handleBinaryExpression(operator);
|
| + } else if ((identical(info, OPEN_PAREN_INFO)) ||
|
| + (identical(info, OPEN_SQUARE_BRACKET_INFO))) {
|
| + token = parseArgumentOrIndexStar(token);
|
| + } else if ((identical(info, PLUS_PLUS_INFO)) ||
|
| + (identical(info, MINUS_MINUS_INFO))) {
|
| + listener.handleUnaryPostfixAssignmentExpression(token);
|
| + token = token.next;
|
| + } else {
|
| + token = listener.unexpected(token);
|
| + }
|
| + } else if (identical(info, IS_INFO)) {
|
| + token = parseIsOperatorRest(token);
|
| + } else if (identical(info, AS_INFO)) {
|
| + token = parseAsOperatorRest(token);
|
| + } else if (identical(info, QUESTION_INFO)) {
|
| + token = parseConditionalExpressionRest(token);
|
| + } else {
|
| + // Left associative, so we recurse at the next higher
|
| + // precedence level.
|
| + token =
|
| + parsePrecedenceExpression(token.next, level + 1, allowCascades);
|
| + listener.handleBinaryExpression(operator);
|
| + }
|
| + info = token.info;
|
| + tokenLevel = info.precedence;
|
| + if (level == EQUALITY_PRECEDENCE || level == RELATIONAL_PRECEDENCE) {
|
| + // We don't allow (a == b == c) or (a < b < c).
|
| + // Continue the outer loop if we have matched one equality or
|
| + // relational operator.
|
| + break;
|
| + }
|
| + }
|
| + }
|
| + return token;
|
| + }
|
| +
|
| + Token parseCascadeExpression(Token token) {
|
| + listener.beginCascade(token);
|
| + assert(optional('..', token));
|
| + Token cascadeOperator = token;
|
| + token = token.next;
|
| + if (optional('[', token)) {
|
| + token = parseArgumentOrIndexStar(token);
|
| + } else if (token.isIdentifier()) {
|
| + token = parseSend(token);
|
| + listener.handleBinaryExpression(cascadeOperator);
|
| + } else {
|
| + return listener.unexpected(token);
|
| + }
|
| + Token mark;
|
| + do {
|
| + mark = token;
|
| + if (optional('.', token)) {
|
| + Token period = token;
|
| + token = parseSend(token.next);
|
| + listener.handleBinaryExpression(period);
|
| + }
|
| + token = parseArgumentOrIndexStar(token);
|
| + } while (!identical(mark, token));
|
| +
|
| + if (identical(token.info.precedence, ASSIGNMENT_PRECEDENCE)) {
|
| + Token assignment = token;
|
| + token = parseExpressionWithoutCascade(token.next);
|
| + listener.handleAssignmentExpression(assignment);
|
| + }
|
| + listener.endCascade();
|
| + return token;
|
| + }
|
| +
|
| + Token parseUnaryExpression(Token token, bool allowCascades) {
|
| + String value = token.stringValue;
|
| + // Prefix:
|
| + if (asyncAwaitKeywordsEnabled && optional('await', token)) {
|
| + return parseAwaitExpression(token, allowCascades);
|
| + } else if (identical(value, '+')) {
|
| + // Dart no longer allows prefix-plus.
|
| + listener.reportError(token, MessageKind.UNSUPPORTED_PREFIX_PLUS);
|
| + return parseUnaryExpression(token.next, allowCascades);
|
| + } else if ((identical(value, '!')) ||
|
| + (identical(value, '-')) ||
|
| + (identical(value, '~'))) {
|
| + Token operator = token;
|
| + // Right associative, so we recurse at the same precedence
|
| + // level.
|
| + token = parsePrecedenceExpression(
|
| + token.next, POSTFIX_PRECEDENCE, allowCascades);
|
| + listener.handleUnaryPrefixExpression(operator);
|
| + } else if ((identical(value, '++')) || identical(value, '--')) {
|
| + // TODO(ahe): Validate this is used correctly.
|
| + Token operator = token;
|
| + // Right associative, so we recurse at the same precedence
|
| + // level.
|
| + token = parsePrecedenceExpression(
|
| + token.next, POSTFIX_PRECEDENCE, allowCascades);
|
| + listener.handleUnaryPrefixAssignmentExpression(operator);
|
| + } else {
|
| + token = parsePrimary(token);
|
| + }
|
| + return token;
|
| + }
|
| +
|
| + Token parseArgumentOrIndexStar(Token token) {
|
| + while (true) {
|
| + if (optional('[', token)) {
|
| + Token openSquareBracket = token;
|
| + bool old = mayParseFunctionExpressions;
|
| + mayParseFunctionExpressions = true;
|
| + token = parseExpression(token.next);
|
| + mayParseFunctionExpressions = old;
|
| + listener.handleIndexedExpression(openSquareBracket, token);
|
| + token = expect(']', token);
|
| + } else if (optional('(', token)) {
|
| + listener.handleNoTypeArguments(token);
|
| + token = parseArguments(token);
|
| + listener.endSend(token);
|
| + } else {
|
| + break;
|
| + }
|
| + }
|
| + return token;
|
| + }
|
| +
|
| + Token parsePrimary(Token token) {
|
| + final kind = token.kind;
|
| + if (kind == IDENTIFIER_TOKEN) {
|
| + return parseSendOrFunctionLiteral(token);
|
| + } else if (kind == INT_TOKEN || kind == HEXADECIMAL_TOKEN) {
|
| + return parseLiteralInt(token);
|
| + } else if (kind == DOUBLE_TOKEN) {
|
| + return parseLiteralDouble(token);
|
| + } else if (kind == STRING_TOKEN) {
|
| + return parseLiteralString(token);
|
| + } else if (kind == HASH_TOKEN) {
|
| + return parseLiteralSymbol(token);
|
| + } else if (kind == KEYWORD_TOKEN) {
|
| + final value = token.stringValue;
|
| + if (value == 'true' || value == 'false') {
|
| + return parseLiteralBool(token);
|
| + } else if (value == 'null') {
|
| + return parseLiteralNull(token);
|
| + } else if (value == 'this') {
|
| + return parseThisExpression(token);
|
| + } else if (value == 'super') {
|
| + return parseSuperExpression(token);
|
| + } else if (value == 'new') {
|
| + return parseNewExpression(token);
|
| + } else if (value == 'const') {
|
| + return parseConstExpression(token);
|
| + } else if (value == 'void') {
|
| + return parseFunctionExpression(token);
|
| + } else if (asyncAwaitKeywordsEnabled &&
|
| + (value == 'yield' || value == 'async')) {
|
| + return listener.expectedExpression(token);
|
| + } else if (token.isIdentifier()) {
|
| + return parseSendOrFunctionLiteral(token);
|
| + } else {
|
| + return listener.expectedExpression(token);
|
| + }
|
| + } else if (kind == OPEN_PAREN_TOKEN) {
|
| + return parseParenthesizedExpressionOrFunctionLiteral(token);
|
| + } else if (kind == OPEN_SQUARE_BRACKET_TOKEN || token.stringValue == '[]') {
|
| + listener.handleNoTypeArguments(token);
|
| + return parseLiteralListSuffix(token, null);
|
| + } else if (kind == OPEN_CURLY_BRACKET_TOKEN) {
|
| + listener.handleNoTypeArguments(token);
|
| + return parseLiteralMapSuffix(token, null);
|
| + } else if (kind == LT_TOKEN) {
|
| + return parseLiteralListOrMapOrFunction(token, null);
|
| + } else {
|
| + return listener.expectedExpression(token);
|
| + }
|
| + }
|
| +
|
| + Token parseParenthesizedExpressionOrFunctionLiteral(Token token) {
|
| + BeginGroupToken beginGroup = token;
|
| + // TODO(eernst): Check for NPE as described in issue 26252.
|
| + Token nextToken = beginGroup.endGroup.next;
|
| + int kind = nextToken.kind;
|
| + if (mayParseFunctionExpressions &&
|
| + (identical(kind, FUNCTION_TOKEN) ||
|
| + identical(kind, OPEN_CURLY_BRACKET_TOKEN) ||
|
| + (identical(kind, KEYWORD_TOKEN) &&
|
| + (nextToken.value == 'async' || nextToken.value == 'sync')))) {
|
| + listener.handleNoTypeVariables(token);
|
| + return parseUnnamedFunction(token);
|
| + } else {
|
| + bool old = mayParseFunctionExpressions;
|
| + mayParseFunctionExpressions = true;
|
| + token = parseParenthesizedExpression(token);
|
| + mayParseFunctionExpressions = old;
|
| + return token;
|
| + }
|
| + }
|
| +
|
| + Token parseParenthesizedExpression(Token token) {
|
| + // We expect [begin] to be of type [BeginGroupToken], but we don't know for
|
| + // sure until after calling expect.
|
| + var begin = token;
|
| + token = expect('(', token);
|
| + // [begin] is now known to have type [BeginGroupToken].
|
| + token = parseExpression(token);
|
| + if (!identical(begin.endGroup, token)) {
|
| + listener.unexpected(token);
|
| + token = begin.endGroup;
|
| + }
|
| + listener.handleParenthesizedExpression(begin);
|
| + return expect(')', token);
|
| + }
|
| +
|
| + Token parseThisExpression(Token token) {
|
| + listener.handleThisExpression(token);
|
| + token = token.next;
|
| + if (optional('(', token)) {
|
| + // Constructor forwarding.
|
| + listener.handleNoTypeArguments(token);
|
| + token = parseArguments(token);
|
| + listener.endSend(token);
|
| + }
|
| + return token;
|
| + }
|
| +
|
| + Token parseSuperExpression(Token token) {
|
| + listener.handleSuperExpression(token);
|
| + token = token.next;
|
| + if (optional('(', token)) {
|
| + // Super constructor.
|
| + listener.handleNoTypeArguments(token);
|
| + token = parseArguments(token);
|
| + listener.endSend(token);
|
| + }
|
| + return token;
|
| + }
|
| +
|
| + /// '[' (expressionList ','?)? ']'.
|
| + ///
|
| + /// Provide [constKeyword] if preceded by 'const', null if not.
|
| + /// This is a suffix parser because it is assumed that type arguments have
|
| + /// been parsed, or `listener.handleNoTypeArguments(..)` has been executed.
|
| + Token parseLiteralListSuffix(Token token, Token constKeyword) {
|
| + assert(optional('[', token) || optional('[]', token));
|
| + Token beginToken = token;
|
| + int count = 0;
|
| + if (optional('[', token)) {
|
| + bool old = mayParseFunctionExpressions;
|
| + mayParseFunctionExpressions = true;
|
| + do {
|
| + if (optional(']', token.next)) {
|
| + token = token.next;
|
| + break;
|
| + }
|
| + token = parseExpression(token.next);
|
| + ++count;
|
| + } while (optional(',', token));
|
| + mayParseFunctionExpressions = old;
|
| + listener.handleLiteralList(count, beginToken, constKeyword, token);
|
| + return expect(']', token);
|
| + }
|
| + // Looking at '[]'.
|
| + listener.handleLiteralList(0, token, constKeyword, token);
|
| + return token.next;
|
| + }
|
| +
|
| + /// '{' (mapLiteralEntry (',' mapLiteralEntry)* ','?)? '}'.
|
| + ///
|
| + /// Provide token for [constKeyword] if preceded by 'const', null if not.
|
| + /// This is a suffix parser because it is assumed that type arguments have
|
| + /// been parsed, or `listener.handleNoTypeArguments(..)` has been executed.
|
| + Token parseLiteralMapSuffix(Token token, Token constKeyword) {
|
| + assert(optional('{', token));
|
| + Token beginToken = token;
|
| + int count = 0;
|
| + bool old = mayParseFunctionExpressions;
|
| + mayParseFunctionExpressions = true;
|
| + do {
|
| + if (optional('}', token.next)) {
|
| + token = token.next;
|
| + break;
|
| + }
|
| + token = parseMapLiteralEntry(token.next);
|
| + ++count;
|
| + } while (optional(',', token));
|
| + mayParseFunctionExpressions = old;
|
| + listener.handleLiteralMap(count, beginToken, constKeyword, token);
|
| + return expect('}', token);
|
| + }
|
| +
|
| + /// formalParameterList functionBody.
|
| + ///
|
| + /// This is a suffix parser because it is assumed that type arguments have
|
| + /// been parsed, or `listener.handleNoTypeArguments(..)` has been executed.
|
| + Token parseLiteralFunctionSuffix(Token token) {
|
| + assert(optional('(', token));
|
| + BeginGroupToken beginGroup = token;
|
| + if (beginGroup.endGroup != null) {
|
| + Token nextToken = beginGroup.endGroup.next;
|
| + int kind = nextToken.kind;
|
| + if (identical(kind, FUNCTION_TOKEN) ||
|
| + identical(kind, OPEN_CURLY_BRACKET_TOKEN) ||
|
| + (identical(kind, KEYWORD_TOKEN) &&
|
| + (nextToken.value == 'async' || nextToken.value == 'sync'))) {
|
| + return parseUnnamedFunction(token);
|
| + }
|
| + // Fall through.
|
| + }
|
| + listener.unexpected(token);
|
| + return null;
|
| + }
|
| +
|
| + /// genericListLiteral | genericMapLiteral | genericFunctionLiteral.
|
| + ///
|
| + /// Where
|
| + /// genericListLiteral ::= typeArguments '[' (expressionList ','?)? ']'
|
| + /// genericMapLiteral ::=
|
| + /// typeArguments '{' (mapLiteralEntry (',' mapLiteralEntry)* ','?)? '}'
|
| + /// genericFunctionLiteral ::=
|
| + /// typeParameters formalParameterList functionBody
|
| + /// Provide token for [constKeyword] if preceded by 'const', null if not.
|
| + Token parseLiteralListOrMapOrFunction(Token token, Token constKeyword) {
|
| + assert(optional('<', token));
|
| + BeginGroupToken begin = token;
|
| + if (enableGenericMethodSyntax &&
|
| + constKeyword == null &&
|
| + begin.endGroup != null &&
|
| + identical(begin.endGroup.next.kind, OPEN_PAREN_TOKEN)) {
|
| + token = parseTypeVariablesOpt(token);
|
| + return parseLiteralFunctionSuffix(token);
|
| + } else {
|
| + token = parseTypeArgumentsOpt(token);
|
| + if (optional('{', token)) {
|
| + return parseLiteralMapSuffix(token, constKeyword);
|
| + } else if ((optional('[', token)) || (optional('[]', token))) {
|
| + return parseLiteralListSuffix(token, constKeyword);
|
| + }
|
| + listener.unexpected(token);
|
| + return null;
|
| + }
|
| + }
|
| +
|
| + Token parseMapLiteralEntry(Token token) {
|
| + listener.beginLiteralMapEntry(token);
|
| + // Assume the listener rejects non-string keys.
|
| + token = parseExpression(token);
|
| + Token colon = token;
|
| + token = expect(':', token);
|
| + token = parseExpression(token);
|
| + listener.endLiteralMapEntry(colon, token);
|
| + return token;
|
| + }
|
| +
|
| + Token parseSendOrFunctionLiteral(Token token) {
|
| + if (!mayParseFunctionExpressions) return parseSend(token);
|
| + Token peek = peekAfterIfType(token);
|
| + if (peek != null &&
|
| + identical(peek.kind, IDENTIFIER_TOKEN) &&
|
| + isFunctionDeclaration(peek.next)) {
|
| + return parseFunctionExpression(token);
|
| + } else if (isFunctionDeclaration(token.next)) {
|
| + return parseFunctionExpression(token);
|
| + } else {
|
| + return parseSend(token);
|
| + }
|
| + }
|
| +
|
| + bool isFunctionDeclaration(Token token) {
|
| + if (enableGenericMethodSyntax && optional('<', token)) {
|
| + BeginGroupToken begin = token;
|
| + if (begin.endGroup == null) return false;
|
| + token = begin.endGroup.next;
|
| + }
|
| + if (optional('(', token)) {
|
| + BeginGroupToken begin = token;
|
| + // TODO(eernst): Check for NPE as described in issue 26252.
|
| + String afterParens = begin.endGroup.next.stringValue;
|
| + if (identical(afterParens, '{') ||
|
| + identical(afterParens, '=>') ||
|
| + identical(afterParens, 'async') ||
|
| + identical(afterParens, 'sync')) {
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + Token parseRequiredArguments(Token token) {
|
| + if (optional('(', token)) {
|
| + token = parseArguments(token);
|
| + } else {
|
| + listener.handleNoArguments(token);
|
| + token = listener.unexpected(token);
|
| + }
|
| + return token;
|
| + }
|
| +
|
| + Token parseNewExpression(Token token) {
|
| + Token newKeyword = token;
|
| + token = expect('new', token);
|
| + token = parseConstructorReference(token);
|
| + token = parseRequiredArguments(token);
|
| + listener.handleNewExpression(newKeyword);
|
| + return token;
|
| + }
|
| +
|
| + Token parseConstExpression(Token token) {
|
| + Token constKeyword = token;
|
| + token = expect('const', token);
|
| + final String value = token.stringValue;
|
| + if ((identical(value, '[')) || (identical(value, '[]'))) {
|
| + listener.handleNoTypeArguments(token);
|
| + return parseLiteralListSuffix(token, constKeyword);
|
| + }
|
| + if (identical(value, '{')) {
|
| + listener.handleNoTypeArguments(token);
|
| + return parseLiteralMapSuffix(token, constKeyword);
|
| + }
|
| + if (identical(value, '<')) {
|
| + return parseLiteralListOrMapOrFunction(token, constKeyword);
|
| + }
|
| + token = parseConstructorReference(token);
|
| + token = parseRequiredArguments(token);
|
| + listener.handleConstExpression(constKeyword);
|
| + return token;
|
| + }
|
| +
|
| + Token parseLiteralInt(Token token) {
|
| + listener.handleLiteralInt(token);
|
| + return token.next;
|
| + }
|
| +
|
| + Token parseLiteralDouble(Token token) {
|
| + listener.handleLiteralDouble(token);
|
| + return token.next;
|
| + }
|
| +
|
| + Token parseLiteralString(Token token) {
|
| + bool old = mayParseFunctionExpressions;
|
| + mayParseFunctionExpressions = true;
|
| + token = parseSingleLiteralString(token);
|
| + int count = 1;
|
| + while (identical(token.kind, STRING_TOKEN)) {
|
| + token = parseSingleLiteralString(token);
|
| + count++;
|
| + }
|
| + if (count > 1) {
|
| + listener.handleStringJuxtaposition(count);
|
| + }
|
| + mayParseFunctionExpressions = old;
|
| + return token;
|
| + }
|
| +
|
| + Token parseLiteralSymbol(Token token) {
|
| + Token hashToken = token;
|
| + listener.beginLiteralSymbol(hashToken);
|
| + token = token.next;
|
| + if (isUserDefinableOperator(token.stringValue)) {
|
| + listener.handleOperator(token);
|
| + listener.endLiteralSymbol(hashToken, 1);
|
| + return token.next;
|
| + } else {
|
| + int count = 1;
|
| + token = parseIdentifier(token);
|
| + while (identical(token.stringValue, '.')) {
|
| + count++;
|
| + token = parseIdentifier(token.next);
|
| + }
|
| + listener.endLiteralSymbol(hashToken, count);
|
| + return token;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Only called when [:token.kind === STRING_TOKEN:].
|
| + */
|
| + Token parseSingleLiteralString(Token token) {
|
| + listener.beginLiteralString(token);
|
| + // Parsing the prefix, for instance 'x of 'x${id}y${id}z'
|
| + token = token.next;
|
| + int interpolationCount = 0;
|
| + var kind = token.kind;
|
| + while (kind != EOF_TOKEN) {
|
| + if (identical(kind, STRING_INTERPOLATION_TOKEN)) {
|
| + // Parsing ${expression}.
|
| + token = token.next;
|
| + token = parseExpression(token);
|
| + token = expect('}', token);
|
| + } else if (identical(kind, STRING_INTERPOLATION_IDENTIFIER_TOKEN)) {
|
| + // Parsing $identifier.
|
| + token = token.next;
|
| + token = parseExpression(token);
|
| + } else {
|
| + break;
|
| + }
|
| + ++interpolationCount;
|
| + // Parsing the infix/suffix, for instance y and z' of 'x${id}y${id}z'
|
| + token = parseStringPart(token);
|
| + kind = token.kind;
|
| + }
|
| + listener.endLiteralString(interpolationCount);
|
| + return token;
|
| + }
|
| +
|
| + Token parseLiteralBool(Token token) {
|
| + listener.handleLiteralBool(token);
|
| + return token.next;
|
| + }
|
| +
|
| + Token parseLiteralNull(Token token) {
|
| + listener.handleLiteralNull(token);
|
| + return token.next;
|
| + }
|
| +
|
| + Token parseSend(Token token) {
|
| + listener.beginSend(token);
|
| + token = parseIdentifier(token);
|
| + if (enableGenericMethodSyntax && isValidMethodTypeArguments(token)) {
|
| + token = parseTypeArgumentsOpt(token);
|
| + } else {
|
| + listener.handleNoTypeArguments(token);
|
| + }
|
| + token = parseArgumentsOpt(token);
|
| + listener.endSend(token);
|
| + return token;
|
| + }
|
| +
|
| + Token parseArgumentsOpt(Token token) {
|
| + if (!optional('(', token)) {
|
| + listener.handleNoArguments(token);
|
| + return token;
|
| + } else {
|
| + return parseArguments(token);
|
| + }
|
| + }
|
| +
|
| + Token parseArguments(Token token) {
|
| + Token begin = token;
|
| + listener.beginArguments(begin);
|
| + assert(identical('(', token.stringValue));
|
| + int argumentCount = 0;
|
| + if (optional(')', token.next)) {
|
| + listener.endArguments(argumentCount, begin, token.next);
|
| + return token.next.next;
|
| + }
|
| + bool old = mayParseFunctionExpressions;
|
| + mayParseFunctionExpressions = true;
|
| + do {
|
| + if (optional(')', token.next)) {
|
| + token = token.next;
|
| + break;
|
| + }
|
| + Token colon = null;
|
| + if (optional(':', token.next.next)) {
|
| + token = parseIdentifier(token.next);
|
| + colon = token;
|
| + }
|
| + token = parseExpression(token.next);
|
| + if (colon != null) listener.handleNamedArgument(colon);
|
| + ++argumentCount;
|
| + } while (optional(',', token));
|
| + mayParseFunctionExpressions = old;
|
| + listener.endArguments(argumentCount, begin, token);
|
| + return expect(')', token);
|
| + }
|
| +
|
| + Token parseIsOperatorRest(Token token) {
|
| + assert(optional('is', token));
|
| + Token operator = token;
|
| + Token not = null;
|
| + if (optional('!', token.next)) {
|
| + token = token.next;
|
| + not = token;
|
| + }
|
| + token = parseType(token.next);
|
| + listener.handleIsOperator(operator, not, token);
|
| + String value = token.stringValue;
|
| + if (identical(value, 'is') || identical(value, 'as')) {
|
| + // The is- and as-operators cannot be chained, but they can take part of
|
| + // expressions like: foo is Foo || foo is Bar.
|
| + listener.unexpected(token);
|
| + }
|
| + return token;
|
| + }
|
| +
|
| + Token parseAsOperatorRest(Token token) {
|
| + assert(optional('as', token));
|
| + Token operator = token;
|
| + token = parseType(token.next);
|
| + listener.handleAsOperator(operator, token);
|
| + String value = token.stringValue;
|
| + if (identical(value, 'is') || identical(value, 'as')) {
|
| + // The is- and as-operators cannot be chained.
|
| + listener.unexpected(token);
|
| + }
|
| + return token;
|
| + }
|
| +
|
| + Token parseVariablesDeclaration(Token token) {
|
| + return parseVariablesDeclarationMaybeSemicolon(token, true);
|
| + }
|
| +
|
| + Token parseVariablesDeclarationNoSemicolon(Token token) {
|
| + // Only called when parsing a for loop, so this is for parsing locals.
|
| + return parseVariablesDeclarationMaybeSemicolon(token, false);
|
| + }
|
| +
|
| + Token parseVariablesDeclarationMaybeSemicolon(
|
| + Token token, bool endWithSemicolon) {
|
| + int count = 1;
|
| + listener.beginVariablesDeclaration(token);
|
| + token = parseModifiers(token);
|
| + token = parseTypeOpt(token);
|
| + token = parseOptionallyInitializedIdentifier(token);
|
| + while (optional(',', token)) {
|
| + token = parseOptionallyInitializedIdentifier(token.next);
|
| + ++count;
|
| + }
|
| + if (endWithSemicolon) {
|
| + Token semicolon = token;
|
| + token = expectSemicolon(semicolon);
|
| + listener.endVariablesDeclaration(count, semicolon);
|
| + return token;
|
| + } else {
|
| + listener.endVariablesDeclaration(count, null);
|
| + return token;
|
| + }
|
| + }
|
| +
|
| + Token parseOptionallyInitializedIdentifier(Token token) {
|
| + listener.beginInitializedIdentifier(token);
|
| + token = parseIdentifier(token);
|
| + token = parseVariableInitializerOpt(token);
|
| + listener.endInitializedIdentifier();
|
| + return token;
|
| + }
|
| +
|
| + Token parseIfStatement(Token token) {
|
| + Token ifToken = token;
|
| + listener.beginIfStatement(ifToken);
|
| + token = expect('if', token);
|
| + token = parseParenthesizedExpression(token);
|
| + token = parseStatement(token);
|
| + Token elseToken = null;
|
| + if (optional('else', token)) {
|
| + elseToken = token;
|
| + token = parseStatement(token.next);
|
| + }
|
| + listener.endIfStatement(ifToken, elseToken);
|
| + return token;
|
| + }
|
| +
|
| + Token parseForStatement(Token awaitToken, Token token) {
|
| + Token forToken = token;
|
| + listener.beginForStatement(forToken);
|
| + token = expect('for', token);
|
| + token = expect('(', token);
|
| + token = parseVariablesDeclarationOrExpressionOpt(token);
|
| + if (optional('in', token)) {
|
| + return parseForInRest(awaitToken, forToken, token);
|
| + } else {
|
| + if (awaitToken != null) {
|
| + listener.reportError(awaitToken, MessageKind.INVALID_AWAIT_FOR);
|
| + }
|
| + return parseForRest(forToken, token);
|
| + }
|
| + }
|
| +
|
| + Token parseVariablesDeclarationOrExpressionOpt(Token token) {
|
| + final String value = token.stringValue;
|
| + if (identical(value, ';')) {
|
| + listener.handleNoExpression(token);
|
| + return token;
|
| + } else if (isOneOf3(token, 'var', 'final', 'const')) {
|
| + return parseVariablesDeclarationNoSemicolon(token);
|
| + }
|
| + Token identifier = peekIdentifierAfterType(token);
|
| + if (identifier != null) {
|
| + assert(identifier.isIdentifier());
|
| + if (isOneOf4(identifier.next, '=', ';', ',', 'in')) {
|
| + return parseVariablesDeclarationNoSemicolon(token);
|
| + }
|
| + }
|
| + return parseExpression(token);
|
| + }
|
| +
|
| + Token parseForRest(Token forToken, Token token) {
|
| + token = expectSemicolon(token);
|
| + if (optional(';', token)) {
|
| + token = parseEmptyStatement(token);
|
| + } else {
|
| + token = parseExpressionStatement(token);
|
| + }
|
| + int expressionCount = 0;
|
| + while (true) {
|
| + if (optional(')', token)) break;
|
| + token = parseExpression(token);
|
| + ++expressionCount;
|
| + if (optional(',', token)) {
|
| + token = token.next;
|
| + } else {
|
| + break;
|
| + }
|
| + }
|
| + token = expect(')', token);
|
| + token = parseStatement(token);
|
| + listener.endForStatement(expressionCount, forToken, token);
|
| + return token;
|
| + }
|
| +
|
| + Token parseForInRest(Token awaitToken, Token forToken, Token token) {
|
| + assert(optional('in', token));
|
| + Token inKeyword = token;
|
| + token = parseExpression(token.next);
|
| + token = expect(')', token);
|
| + token = parseStatement(token);
|
| + listener.endForIn(awaitToken, forToken, inKeyword, token);
|
| + return token;
|
| + }
|
| +
|
| + Token parseWhileStatement(Token token) {
|
| + Token whileToken = token;
|
| + listener.beginWhileStatement(whileToken);
|
| + token = expect('while', token);
|
| + token = parseParenthesizedExpression(token);
|
| + token = parseStatement(token);
|
| + listener.endWhileStatement(whileToken, token);
|
| + return token;
|
| + }
|
| +
|
| + Token parseDoWhileStatement(Token token) {
|
| + Token doToken = token;
|
| + listener.beginDoWhileStatement(doToken);
|
| + token = expect('do', token);
|
| + token = parseStatement(token);
|
| + Token whileToken = token;
|
| + token = expect('while', token);
|
| + token = parseParenthesizedExpression(token);
|
| + listener.endDoWhileStatement(doToken, whileToken, token);
|
| + return expectSemicolon(token);
|
| + }
|
| +
|
| + Token parseBlock(Token token) {
|
| + Token begin = token;
|
| + listener.beginBlock(begin);
|
| + int statementCount = 0;
|
| + token = expect('{', token);
|
| + while (notEofOrValue('}', token)) {
|
| + token = parseStatement(token);
|
| + ++statementCount;
|
| + }
|
| + listener.endBlock(statementCount, begin, token);
|
| + return expect('}', token);
|
| + }
|
| +
|
| + Token parseAwaitExpression(Token token, bool allowCascades) {
|
| + Token awaitToken = token;
|
| + listener.beginAwaitExpression(awaitToken);
|
| + token = expect('await', token);
|
| + token = parsePrecedenceExpression(token, POSTFIX_PRECEDENCE, allowCascades);
|
| + listener.endAwaitExpression(awaitToken, token);
|
| + return token;
|
| + }
|
| +
|
| + Token parseThrowExpression(Token token, bool allowCascades) {
|
| + Token throwToken = token;
|
| + listener.beginThrowExpression(throwToken);
|
| + token = expect('throw', token);
|
| + token = allowCascades
|
| + ? parseExpression(token)
|
| + : parseExpressionWithoutCascade(token);
|
| + listener.endThrowExpression(throwToken, token);
|
| + return token;
|
| + }
|
| +
|
| + Token parseRethrowStatement(Token token) {
|
| + Token throwToken = token;
|
| + listener.beginRethrowStatement(throwToken);
|
| + // TODO(kasperl): Disallow throw here.
|
| + if (identical(throwToken.stringValue, 'throw')) {
|
| + token = expect('throw', token);
|
| + } else {
|
| + token = expect('rethrow', token);
|
| + }
|
| + listener.endRethrowStatement(throwToken, token);
|
| + return expectSemicolon(token);
|
| + }
|
| +
|
| + Token parseTryStatement(Token token) {
|
| + assert(optional('try', token));
|
| + Token tryKeyword = token;
|
| + listener.beginTryStatement(tryKeyword);
|
| + token = parseBlock(token.next);
|
| + int catchCount = 0;
|
| +
|
| + String value = token.stringValue;
|
| + while (identical(value, 'catch') || identical(value, 'on')) {
|
| + var onKeyword = null;
|
| + if (identical(value, 'on')) {
|
| + // on qualified catchPart?
|
| + onKeyword = token;
|
| + token = parseType(token.next);
|
| + value = token.stringValue;
|
| + }
|
| + Token catchKeyword = null;
|
| + if (identical(value, 'catch')) {
|
| + catchKeyword = token;
|
| + // TODO(ahe): Validate the "parameters".
|
| + token = parseFormalParameters(token.next);
|
| + }
|
| + token = parseBlock(token);
|
| + ++catchCount;
|
| + listener.handleCatchBlock(onKeyword, catchKeyword);
|
| + value = token.stringValue; // while condition
|
| + }
|
| +
|
| + Token finallyKeyword = null;
|
| + if (optional('finally', token)) {
|
| + finallyKeyword = token;
|
| + token = parseBlock(token.next);
|
| + listener.handleFinallyBlock(finallyKeyword);
|
| + }
|
| + listener.endTryStatement(catchCount, tryKeyword, finallyKeyword);
|
| + return token;
|
| + }
|
| +
|
| + Token parseSwitchStatement(Token token) {
|
| + assert(optional('switch', token));
|
| + Token switchKeyword = token;
|
| + listener.beginSwitchStatement(switchKeyword);
|
| + token = parseParenthesizedExpression(token.next);
|
| + token = parseSwitchBlock(token);
|
| + listener.endSwitchStatement(switchKeyword, token);
|
| + return token.next;
|
| + }
|
| +
|
| + Token parseSwitchBlock(Token token) {
|
| + Token begin = token;
|
| + listener.beginSwitchBlock(begin);
|
| + token = expect('{', token);
|
| + int caseCount = 0;
|
| + while (!identical(token.kind, EOF_TOKEN)) {
|
| + if (optional('}', token)) {
|
| + break;
|
| + }
|
| + token = parseSwitchCase(token);
|
| + ++caseCount;
|
| + }
|
| + listener.endSwitchBlock(caseCount, begin, token);
|
| + expect('}', token);
|
| + return token;
|
| + }
|
| +
|
| + /**
|
| + * Peek after the following labels (if any). The following token
|
| + * is used to determine if the labels belong to a statement or a
|
| + * switch case.
|
| + */
|
| + Token peekPastLabels(Token token) {
|
| + while (token.isIdentifier() && optional(':', token.next)) {
|
| + token = token.next.next;
|
| + }
|
| + return token;
|
| + }
|
| +
|
| + /**
|
| + * Parse a group of labels, cases and possibly a default keyword and
|
| + * the statements that they select.
|
| + */
|
| + Token parseSwitchCase(Token token) {
|
| + Token begin = token;
|
| + Token defaultKeyword = null;
|
| + int expressionCount = 0;
|
| + int labelCount = 0;
|
| + Token peek = peekPastLabels(token);
|
| + while (true) {
|
| + // Loop until we find something that can't be part of a switch case.
|
| + String value = peek.stringValue;
|
| + if (identical(value, 'default')) {
|
| + while (!identical(token, peek)) {
|
| + token = parseLabel(token);
|
| + labelCount++;
|
| + }
|
| + defaultKeyword = token;
|
| + token = expect(':', token.next);
|
| + peek = token;
|
| + break;
|
| + } else if (identical(value, 'case')) {
|
| + while (!identical(token, peek)) {
|
| + token = parseLabel(token);
|
| + labelCount++;
|
| + }
|
| + Token caseKeyword = token;
|
| + token = parseExpression(token.next);
|
| + Token colonToken = token;
|
| + token = expect(':', token);
|
| + listener.handleCaseMatch(caseKeyword, colonToken);
|
| + expressionCount++;
|
| + peek = peekPastLabels(token);
|
| + } else {
|
| + if (expressionCount == 0) {
|
| + listener.expected("case", token);
|
| + }
|
| + break;
|
| + }
|
| + }
|
| + // Finally zero or more statements.
|
| + int statementCount = 0;
|
| + while (!identical(token.kind, EOF_TOKEN)) {
|
| + String value = peek.stringValue;
|
| + if ((identical(value, 'case')) ||
|
| + (identical(value, 'default')) ||
|
| + ((identical(value, '}')) && (identical(token, peek)))) {
|
| + // A label just before "}" will be handled as a statement error.
|
| + break;
|
| + } else {
|
| + token = parseStatement(token);
|
| + }
|
| + statementCount++;
|
| + peek = peekPastLabels(token);
|
| + }
|
| + listener.handleSwitchCase(labelCount, expressionCount, defaultKeyword,
|
| + statementCount, begin, token);
|
| + return token;
|
| + }
|
| +
|
| + Token parseBreakStatement(Token token) {
|
| + assert(optional('break', token));
|
| + Token breakKeyword = token;
|
| + token = token.next;
|
| + bool hasTarget = false;
|
| + if (token.isIdentifier()) {
|
| + token = parseIdentifier(token);
|
| + hasTarget = true;
|
| + }
|
| + listener.handleBreakStatement(hasTarget, breakKeyword, token);
|
| + return expectSemicolon(token);
|
| + }
|
| +
|
| + Token parseAssertStatement(Token token) {
|
| + Token assertKeyword = token;
|
| + Token commaToken = null;
|
| + token = expect('assert', token);
|
| + token = expect('(', token);
|
| + bool old = mayParseFunctionExpressions;
|
| + mayParseFunctionExpressions = true;
|
| + token = parseExpression(token);
|
| + if (optional(',', token)) {
|
| + commaToken = token;
|
| + token = token.next;
|
| + token = parseExpression(token);
|
| + }
|
| + token = expect(')', token);
|
| + mayParseFunctionExpressions = old;
|
| + listener.handleAssertStatement(assertKeyword, commaToken, token);
|
| + return expectSemicolon(token);
|
| + }
|
| +
|
| + Token parseContinueStatement(Token token) {
|
| + assert(optional('continue', token));
|
| + Token continueKeyword = token;
|
| + token = token.next;
|
| + bool hasTarget = false;
|
| + if (token.isIdentifier()) {
|
| + token = parseIdentifier(token);
|
| + hasTarget = true;
|
| + }
|
| + listener.handleContinueStatement(hasTarget, continueKeyword, token);
|
| + return expectSemicolon(token);
|
| + }
|
| +
|
| + Token parseEmptyStatement(Token token) {
|
| + listener.handleEmptyStatement(token);
|
| + return expectSemicolon(token);
|
| + }
|
| +}
|
|
|