| Index: pkg/analyzer/lib/src/generated/parser.dart
|
| diff --git a/pkg/analyzer/lib/src/generated/parser.dart b/pkg/analyzer/lib/src/generated/parser.dart
|
| index 672f761f6fae7fd37641fdde925d4bb845fc466b..7615c1bb946841298fe506ccf597f29f6bdc11f0 100644
|
| --- a/pkg/analyzer/lib/src/generated/parser.dart
|
| +++ b/pkg/analyzer/lib/src/generated/parser.dart
|
| @@ -26,458 +26,6 @@ import 'package:analyzer/src/generated/utilities_dart.dart';
|
|
|
| export 'package:analyzer/src/dart/ast/utilities.dart' show ResolutionCopier;
|
|
|
| -Map<String, MethodTrampoline> methodTable_Parser = <String, MethodTrampoline>{
|
| - 'parseCompilationUnit_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.parseCompilationUnit(arg0)),
|
| - 'parseDirectives_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.parseDirectives(arg0)),
|
| - 'parseExpression_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.parseExpression(arg0)),
|
| - 'parseStatement_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.parseStatement(arg0)),
|
| - 'parseStatements_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.parseStatements(arg0)),
|
| - 'parseAnnotation_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseAnnotation()),
|
| - 'parseArgument_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseArgument()),
|
| - 'parseArgumentList_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseArgumentList()),
|
| - 'parseBitwiseOrExpression_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseBitwiseOrExpression()),
|
| - 'parseBlock_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseBlock()),
|
| - 'parseClassMember_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.parseClassMember(arg0)),
|
| - 'parseCompilationUnit_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseCompilationUnit2()),
|
| - 'parseConditionalExpression_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseConditionalExpression()),
|
| - 'parseConstructorName_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseConstructorName()),
|
| - 'parseExpression_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseExpression2()),
|
| - 'parseExpressionWithoutCascade_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseExpressionWithoutCascade()),
|
| - 'parseExtendsClause_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseExtendsClause()),
|
| - 'parseFormalParameterList_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseFormalParameterList()),
|
| - 'parseFunctionExpression_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseFunctionExpression()),
|
| - 'parseImplementsClause_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseImplementsClause()),
|
| - 'parseLabel_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseLabel()),
|
| - 'parseLibraryIdentifier_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseLibraryIdentifier()),
|
| - 'parseLogicalOrExpression_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseLogicalOrExpression()),
|
| - 'parseMapLiteralEntry_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseMapLiteralEntry()),
|
| - 'parseNormalFormalParameter_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseNormalFormalParameter()),
|
| - 'parsePrefixedIdentifier_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parsePrefixedIdentifier()),
|
| - 'parseReturnType_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseReturnType()),
|
| - 'parseSimpleIdentifier_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseSimpleIdentifier()),
|
| - 'parseStatement_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseStatement2()),
|
| - 'parseStringLiteral_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseStringLiteral()),
|
| - 'parseTypeArgumentList_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseTypeArgumentList()),
|
| - 'parseTypeName_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseTypeName(false)),
|
| - 'parseTypeParameter_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseTypeParameter()),
|
| - 'parseTypeParameterList_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseTypeParameterList()),
|
| - 'parseWithClause_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseWithClause()),
|
| - 'advance_0': new MethodTrampoline(0, (Parser target) => target._advance()),
|
| - 'appendScalarValue_5': new MethodTrampoline(
|
| - 5,
|
| - (Parser target, arg0, arg1, arg2, arg3, arg4) =>
|
| - target._appendScalarValue(arg0, arg1, arg2, arg3, arg4)),
|
| - 'convertToFunctionDeclaration_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._convertToFunctionDeclaration(arg0)),
|
| - 'couldBeStartOfCompilationUnitMember_0': new MethodTrampoline(
|
| - 0, (Parser target) => target._couldBeStartOfCompilationUnitMember()),
|
| - 'createSyntheticIdentifier_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.createSyntheticIdentifier()),
|
| - 'createSyntheticKeyword_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._createSyntheticKeyword(arg0)),
|
| - 'createSyntheticStringLiteral_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.createSyntheticStringLiteral()),
|
| - 'createSyntheticToken_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._createSyntheticToken(arg0)),
|
| - 'ensureAssignable_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._ensureAssignable(arg0)),
|
| - 'expect_1':
|
| - new MethodTrampoline(1, (Parser target, arg0) => target._expect(arg0)),
|
| - 'expectGt_0': new MethodTrampoline(0, (Parser target) => target._expectGt()),
|
| - 'expectKeyword_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._expectKeyword(arg0)),
|
| - 'findRange_2': new MethodTrampoline(
|
| - 2,
|
| - (Parser target, List<List<int>> arg0, int arg1) =>
|
| - target._findRange(arg0, arg1)),
|
| - 'getCodeBlockRanges_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._getCodeBlockRanges(arg0)),
|
| - 'getEndToken_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._getEndToken(arg0)),
|
| - 'injectToken_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._injectToken(arg0)),
|
| - 'isFunctionDeclaration_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.isFunctionDeclaration()),
|
| - 'isFunctionExpression_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.isFunctionExpression(arg0)),
|
| - 'isHexDigit_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._isHexDigit(arg0)),
|
| - 'isInitializedVariableDeclaration_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.isInitializedVariableDeclaration()),
|
| - 'isLinkText_2': new MethodTrampoline(
|
| - 2, (Parser target, arg0, arg1) => target._isLinkText(arg0, arg1)),
|
| - 'isOperator_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._isOperator(arg0)),
|
| - 'isSwitchMember_0':
|
| - new MethodTrampoline(0, (Parser target) => target.isSwitchMember()),
|
| - 'isTypedIdentifier_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._isTypedIdentifier(arg0)),
|
| - 'lockErrorListener_0':
|
| - new MethodTrampoline(0, (Parser target) => target._lockErrorListener()),
|
| - 'matches_1':
|
| - new MethodTrampoline(1, (Parser target, arg0) => target._matches(arg0)),
|
| - 'matchesGt_0':
|
| - new MethodTrampoline(0, (Parser target) => target._matchesGt()),
|
| - 'matchesIdentifier_0':
|
| - new MethodTrampoline(0, (Parser target) => target._matchesIdentifier()),
|
| - 'matchesKeyword_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._matchesKeyword(arg0)),
|
| - 'matchesString_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._matchesString(arg0)),
|
| - 'optional_1':
|
| - new MethodTrampoline(1, (Parser target, arg0) => target._optional(arg0)),
|
| - 'parseAdditiveExpression_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseAdditiveExpression()),
|
| - 'parseAssertStatement_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseAssertStatement()),
|
| - 'parseAssignableExpression_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.parseAssignableExpression(arg0)),
|
| - 'parseAssignableSelector_2': new MethodTrampoline(
|
| - 2,
|
| - (Parser target, arg0, arg1) =>
|
| - target._parseAssignableSelector(arg0, arg1)),
|
| - 'parseAwaitExpression_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseAwaitExpression()),
|
| - 'parseBitwiseAndExpression_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseBitwiseAndExpression()),
|
| - 'parseBitwiseXorExpression_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseBitwiseXorExpression()),
|
| - 'parseBreakStatement_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseBreakStatement()),
|
| - 'parseCascadeSection_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseCascadeSection()),
|
| - 'parseClassDeclaration_2': new MethodTrampoline(2,
|
| - (Parser target, arg0, arg1) => target.parseClassDeclaration(arg0, arg1)),
|
| - 'parseClassMembers_2': new MethodTrampoline(
|
| - 2, (Parser target, arg0, arg1) => target._parseClassMembers(arg0, arg1)),
|
| - 'parseClassTypeAlias_3': new MethodTrampoline(
|
| - 3,
|
| - (Parser target, arg0, arg1, arg2) =>
|
| - target.parseClassTypeAlias(arg0, arg1, arg2)),
|
| - 'parseCombinator_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseCombinator()),
|
| - 'parseCombinators_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseCombinators()),
|
| - 'parseCommentAndMetadata_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseCommentAndMetadata()),
|
| - 'parseCommentReference_2': new MethodTrampoline(2,
|
| - (Parser target, arg0, arg1) => target.parseCommentReference(arg0, arg1)),
|
| - 'parseCommentReferences_1': new MethodTrampoline(
|
| - 1,
|
| - (Parser target, List<DocumentationCommentToken> arg0) =>
|
| - target._parseCommentReferences(arg0)),
|
| - 'parseCompilationUnitMember_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.parseCompilationUnitMember(arg0)),
|
| - 'parseConfiguration_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseConfiguration()),
|
| - 'parseConstExpression_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseConstExpression()),
|
| - 'parseConstructor_8': new MethodTrampoline(
|
| - 8,
|
| - (Parser target, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) => target
|
| - ._parseConstructor(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7)),
|
| - 'parseConstructorFieldInitializer_1': new MethodTrampoline(1,
|
| - (Parser target, arg0) => target._parseConstructorFieldInitializer(arg0)),
|
| - 'parseContinueStatement_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseContinueStatement()),
|
| - 'parseDirective_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._parseDirective(arg0)),
|
| - 'parseDirectives_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseDirectives2()),
|
| - 'parseDocumentationComment_0': new MethodTrampoline(0, (Parser target) {
|
| - List<DocumentationCommentToken> tokens =
|
| - target.parseDocumentationCommentTokens();
|
| - return target.parseDocumentationComment(tokens);
|
| - }),
|
| - 'parseDoStatement_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseDoStatement()),
|
| - 'parseDottedName_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseDottedName()),
|
| - 'parseEmptyStatement_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseEmptyStatement()),
|
| - 'parseEnumConstantDeclaration_0': new MethodTrampoline(
|
| - 0, (Parser target) => target._parseEnumConstantDeclaration()),
|
| - 'parseEnumDeclaration_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.parseEnumDeclaration(arg0)),
|
| - 'parseEqualityExpression_0': new MethodTrampoline(
|
| - 0, (Parser target) => target._parseEqualityExpression()),
|
| - 'parseExportDirective_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._parseExportDirective(arg0)),
|
| - 'parseExpressionList_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseExpressionList()),
|
| - 'parseFinalConstVarOrType_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.parseFinalConstVarOrType(arg0)),
|
| - 'parseFormalParameter_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._parseFormalParameter(arg0)),
|
| - 'parseForStatement_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseForStatement()),
|
| - 'parseFunctionBody_3': new MethodTrampoline(
|
| - 3,
|
| - (Parser target, arg0, arg1, arg2) =>
|
| - target.parseFunctionBody(arg0, arg1, arg2)),
|
| - 'parseFunctionDeclaration_3': new MethodTrampoline(
|
| - 3,
|
| - (Parser target, arg0, arg1, arg2) =>
|
| - target._parseFunctionDeclaration(arg0, arg1, arg2)),
|
| - 'parseFunctionDeclarationStatement_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseFunctionDeclarationStatement()),
|
| - 'parseFunctionDeclarationStatementAfterReturnType_2': new MethodTrampoline(
|
| - 2,
|
| - (Parser target, arg0, arg1) =>
|
| - target._parseFunctionDeclarationStatementAfterReturnType(arg0, arg1)),
|
| - 'parseFunctionTypeAlias_2': new MethodTrampoline(
|
| - 2,
|
| - (Parser target, arg0, arg1) =>
|
| - target._parseFunctionTypeAlias(arg0, arg1)),
|
| - 'parseGetter_4': new MethodTrampoline(
|
| - 4,
|
| - (Parser target, arg0, arg1, arg2, arg3) =>
|
| - target._parseGetter(arg0, arg1, arg2, arg3)),
|
| - 'parseIdentifierList_0':
|
| - new MethodTrampoline(0, (Parser target) => target._parseIdentifierList()),
|
| - 'parseIfStatement_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseIfStatement()),
|
| - 'parseImportDirective_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._parseImportDirective(arg0)),
|
| - 'parseInitializedIdentifierList_4': new MethodTrampoline(
|
| - 4,
|
| - (Parser target, arg0, arg1, arg2, arg3) =>
|
| - target._parseInitializedIdentifierList(arg0, arg1, arg2, arg3)),
|
| - 'parseInstanceCreationExpression_1': new MethodTrampoline(1,
|
| - (Parser target, arg0) => target._parseInstanceCreationExpression(arg0)),
|
| - 'parseLibraryDirective_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._parseLibraryDirective(arg0)),
|
| - 'parseLibraryName_2': new MethodTrampoline(
|
| - 2, (Parser target, arg0, arg1) => target._parseLibraryName(arg0, arg1)),
|
| - 'parseListLiteral_2': new MethodTrampoline(
|
| - 2, (Parser target, arg0, arg1) => target._parseListLiteral(arg0, arg1)),
|
| - 'parseListOrMapLiteral_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.parseListOrMapLiteral(arg0)),
|
| - 'parseLogicalAndExpression_0': new MethodTrampoline(
|
| - 0, (Parser target) => target._parseLogicalAndExpression()),
|
| - 'parseMapLiteral_2': new MethodTrampoline(
|
| - 2, (Parser target, arg0, arg1) => target._parseMapLiteral(arg0, arg1)),
|
| - 'parseMethodDeclarationAfterParameters_7': new MethodTrampoline(
|
| - 7,
|
| - (Parser target, arg0, arg1, arg2, arg3, arg4, arg5, arg6) =>
|
| - target._parseMethodDeclarationAfterParameters(
|
| - arg0, arg1, arg2, arg3, arg4, arg5, arg6)),
|
| - 'parseMethodDeclarationAfterReturnType_4': new MethodTrampoline(
|
| - 4,
|
| - (Parser target, arg0, arg1, arg2, arg3) => target
|
| - ._parseMethodDeclarationAfterReturnType(arg0, arg1, arg2, arg3)),
|
| - 'parseModifiers_0':
|
| - new MethodTrampoline(0, (Parser target) => target._parseModifiers()),
|
| - 'parseMultiplicativeExpression_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseMultiplicativeExpression()),
|
| - 'parseNativeClause_0':
|
| - new MethodTrampoline(0, (Parser target) => target._parseNativeClause()),
|
| - 'parseNewExpression_0':
|
| - new MethodTrampoline(0, (Parser target) => target._parseNewExpression()),
|
| - 'parseNonLabeledStatement_0': new MethodTrampoline(
|
| - 0, (Parser target) => target._parseNonLabeledStatement()),
|
| - 'parseOperator_3': new MethodTrampoline(
|
| - 3,
|
| - (Parser target, arg0, arg1, arg2) =>
|
| - target.parseOperator(arg0, arg1, arg2)),
|
| - 'parseOptionalReturnType_0': new MethodTrampoline(
|
| - 0, (Parser target) => target._parseOptionalReturnType()),
|
| - 'parsePartDirective_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._parsePartOrPartOfDirective(arg0)),
|
| - 'parsePostfixExpression_0': new MethodTrampoline(
|
| - 0, (Parser target) => target._parsePostfixExpression()),
|
| - 'parsePrimaryExpression_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parsePrimaryExpression()),
|
| - 'parseRedirectingConstructorInvocation_1': new MethodTrampoline(
|
| - 1,
|
| - (Parser target, arg0) =>
|
| - target._parseRedirectingConstructorInvocation(arg0)),
|
| - 'parseRelationalExpression_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseRelationalExpression()),
|
| - 'parseRethrowExpression_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseRethrowExpression()),
|
| - 'parseReturnStatement_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseReturnStatement()),
|
| - 'parseSetter_4': new MethodTrampoline(
|
| - 4,
|
| - (Parser target, arg0, arg1, arg2, arg3) =>
|
| - target._parseSetter(arg0, arg1, arg2, arg3)),
|
| - 'parseShiftExpression_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseShiftExpression()),
|
| - 'parseStatementList_0':
|
| - new MethodTrampoline(0, (Parser target) => target._parseStatementList()),
|
| - 'parseStringInterpolation_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._parseStringInterpolation(arg0)),
|
| - 'parseSuperConstructorInvocation_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseSuperConstructorInvocation()),
|
| - 'parseSwitchStatement_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseSwitchStatement()),
|
| - 'parseSymbolLiteral_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseSymbolLiteral()),
|
| - 'parseThrowExpression_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseThrowExpression()),
|
| - 'parseThrowExpressionWithoutCascade_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseThrowExpressionWithoutCascade()),
|
| - 'parseTryStatement_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseTryStatement()),
|
| - 'parseTypeAlias_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._parseTypeAlias(arg0)),
|
| - 'parseUnaryExpression_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseUnaryExpression()),
|
| - 'parseVariableDeclaration_0': new MethodTrampoline(
|
| - 0, (Parser target) => target.parseVariableDeclaration()),
|
| - 'parseVariableDeclarationListAfterMetadata_1': new MethodTrampoline(
|
| - 1,
|
| - (Parser target, arg0) =>
|
| - target.parseVariableDeclarationListAfterMetadata(arg0)),
|
| - 'parseVariableDeclarationListAfterType_3': new MethodTrampoline(
|
| - 3,
|
| - (Parser target, arg0, arg1, arg2) =>
|
| - target.parseVariableDeclarationListAfterType(arg0, arg1, arg2)),
|
| - 'parseVariableDeclarationStatementAfterMetadata_1': new MethodTrampoline(
|
| - 1,
|
| - (Parser target, arg0) =>
|
| - target.parseVariableDeclarationStatementAfterMetadata(arg0)),
|
| - 'parseVariableDeclarationStatementAfterType_3': new MethodTrampoline(
|
| - 3,
|
| - (Parser target, arg0, arg1, arg2) =>
|
| - target._parseVariableDeclarationStatementAfterType(arg0, arg1, arg2)),
|
| - 'parseWhileStatement_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseWhileStatement()),
|
| - 'parseYieldStatement_0':
|
| - new MethodTrampoline(0, (Parser target) => target.parseYieldStatement()),
|
| - 'peek_0': new MethodTrampoline(0, (Parser target) => target._peek()),
|
| - 'peekAt_1':
|
| - new MethodTrampoline(1, (Parser target, arg0) => target._peekAt(arg0)),
|
| - 'reportError_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._reportError(arg0)),
|
| - 'reportErrorForCurrentToken_2': new MethodTrampoline(
|
| - 2,
|
| - (Parser target, arg0, arg1) =>
|
| - target._reportErrorForCurrentToken(arg0, arg1)),
|
| - 'reportErrorForNode_3': new MethodTrampoline(
|
| - 3,
|
| - (Parser target, arg0, arg1, arg2) =>
|
| - target._reportErrorForNode(arg0, arg1, arg2)),
|
| - 'reportErrorForToken_3': new MethodTrampoline(
|
| - 3,
|
| - (Parser target, arg0, arg1, arg2) =>
|
| - target._reportErrorForToken(arg0, arg1, arg2)),
|
| - 'skipBlock_0':
|
| - new MethodTrampoline(0, (Parser target) => target._skipBlock()),
|
| - 'skipFinalConstVarOrType_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._skipFinalConstVarOrType(arg0)),
|
| - 'skipFormalParameterList_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._skipFormalParameterList(arg0)),
|
| - 'skipPastMatchingToken_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._skipPastMatchingToken(arg0)),
|
| - 'skipPrefixedIdentifier_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.skipPrefixedIdentifier(arg0)),
|
| - 'skipReturnType_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.skipReturnType(arg0)),
|
| - 'skipSimpleIdentifier_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.skipSimpleIdentifier(arg0)),
|
| - 'skipStringInterpolation_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._skipStringInterpolation(arg0)),
|
| - 'skipStringLiteral_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.skipStringLiteral(arg0)),
|
| - 'skipTypeArgumentList_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.skipTypeArgumentList(arg0)),
|
| - 'skipTypeName_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target.skipTypeName(arg0)),
|
| - 'skipTypeParameterList_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._skipTypeParameterList(arg0)),
|
| - 'tokenMatches_2': new MethodTrampoline(
|
| - 2, (Parser target, arg0, arg1) => target._tokenMatches(arg0, arg1)),
|
| - 'tokenMatchesIdentifier_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._tokenMatchesIdentifier(arg0)),
|
| - 'tokenMatchesKeyword_2': new MethodTrampoline(2,
|
| - (Parser target, arg0, arg1) => target._tokenMatchesKeyword(arg0, arg1)),
|
| - 'tokenMatchesString_2': new MethodTrampoline(
|
| - 2, (Parser target, arg0, arg1) => target._tokenMatchesString(arg0, arg1)),
|
| - 'unlockErrorListener_0':
|
| - new MethodTrampoline(0, (Parser target) => target._unlockErrorListener()),
|
| - 'validateFormalParameterList_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._validateFormalParameterList(arg0)),
|
| - 'validateModifiersForClass_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._validateModifiersForClass(arg0)),
|
| - 'validateModifiersForConstructor_1': new MethodTrampoline(1,
|
| - (Parser target, arg0) => target._validateModifiersForConstructor(arg0)),
|
| - 'validateModifiersForEnum_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._validateModifiersForEnum(arg0)),
|
| - 'validateModifiersForField_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._validateModifiersForField(arg0)),
|
| - 'validateModifiersForFunctionDeclarationStatement_1': new MethodTrampoline(
|
| - 1,
|
| - (Parser target, arg0) =>
|
| - target._validateModifiersForFunctionDeclarationStatement(arg0)),
|
| - 'validateModifiersForGetterOrSetterOrMethod_1': new MethodTrampoline(
|
| - 1,
|
| - (Parser target, arg0) =>
|
| - target._validateModifiersForGetterOrSetterOrMethod(arg0)),
|
| - 'validateModifiersForOperator_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._validateModifiersForOperator(arg0)),
|
| - 'validateModifiersForTopLevelDeclaration_1': new MethodTrampoline(
|
| - 1,
|
| - (Parser target, arg0) =>
|
| - target._validateModifiersForTopLevelDeclaration(arg0)),
|
| - 'validateModifiersForTopLevelFunction_1': new MethodTrampoline(
|
| - 1,
|
| - (Parser target, arg0) =>
|
| - target._validateModifiersForTopLevelFunction(arg0)),
|
| - 'validateModifiersForTopLevelVariable_1': new MethodTrampoline(
|
| - 1,
|
| - (Parser target, arg0) =>
|
| - target._validateModifiersForTopLevelVariable(arg0)),
|
| - 'validateModifiersForTypedef_1': new MethodTrampoline(
|
| - 1, (Parser target, arg0) => target._validateModifiersForTypedef(arg0)),
|
| -};
|
| -
|
| -Object invokeParserMethodImpl(
|
| - Parser parser, String methodName, List<Object> objects) {
|
| - MethodTrampoline method =
|
| - methodTable_Parser['${methodName}_${objects.length}'];
|
| - if (method == null) {
|
| - throw new ArgumentError('There is no method named $methodName');
|
| - }
|
| - return method.invoke(parser, objects);
|
| -}
|
| -
|
| /**
|
| * A simple data-holder for a method that needs to return multiple values.
|
| */
|
| @@ -783,7 +331,7 @@ class Parser {
|
| * Return `true` if the current token is the first token of a return type that
|
| * is followed by an identifier, possibly followed by a list of type
|
| * parameters, followed by a left-parenthesis. This is used by
|
| - * [_parseTypeAlias] to determine whether or not to parse a return type.
|
| + * [parseTypeAlias] to determine whether or not to parse a return type.
|
| */
|
| @deprecated
|
| bool get hasReturnTypeInTypeAlias {
|
| @@ -1219,7 +767,7 @@ class Parser {
|
| */
|
| Expression parseAssignableExpression(bool primaryAllowed) {
|
| if (_matchesKeyword(Keyword.SUPER)) {
|
| - return _parseAssignableSelector(
|
| + return parseAssignableSelector(
|
| new SuperExpression(getAndAdvance()), false,
|
| allowConditional: false);
|
| }
|
| @@ -1227,6 +775,57 @@ class Parser {
|
| }
|
|
|
| /**
|
| + * Parse an assignable selector. The [prefix] is the expression preceding the
|
| + * selector. The [optional] is `true` if the selector is optional. Return the
|
| + * assignable selector that was parsed, or the original prefix if there was no
|
| + * assignable selector. If [allowConditional] is false, then the '?.'
|
| + * operator will still be parsed, but a parse error will be generated.
|
| + *
|
| + * unconditionalAssignableSelector ::=
|
| + * '[' expression ']'
|
| + * | '.' identifier
|
| + *
|
| + * assignableSelector ::=
|
| + * unconditionalAssignableSelector
|
| + * | '?.' identifier
|
| + */
|
| + Expression parseAssignableSelector(Expression prefix, bool optional,
|
| + {bool allowConditional: true}) {
|
| + TokenType type = _currentToken.type;
|
| + if (type == TokenType.OPEN_SQUARE_BRACKET) {
|
| + Token leftBracket = getAndAdvance();
|
| + bool wasInInitializer = _inInitializer;
|
| + _inInitializer = false;
|
| + try {
|
| + Expression index = parseExpression2();
|
| + Token rightBracket = _expect(TokenType.CLOSE_SQUARE_BRACKET);
|
| + return new IndexExpression.forTarget(
|
| + prefix, leftBracket, index, rightBracket);
|
| + } finally {
|
| + _inInitializer = wasInInitializer;
|
| + }
|
| + } else {
|
| + bool isQuestionPeriod = type == TokenType.QUESTION_PERIOD;
|
| + if (type == TokenType.PERIOD || isQuestionPeriod) {
|
| + if (isQuestionPeriod && !allowConditional) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.INVALID_OPERATOR_FOR_SUPER,
|
| + [_currentToken.lexeme]);
|
| + }
|
| + Token operator = getAndAdvance();
|
| + return new PropertyAccess(prefix, operator, parseSimpleIdentifier());
|
| + } else {
|
| + if (!optional) {
|
| + // Report the missing selector.
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.MISSING_ASSIGNABLE_SELECTOR);
|
| + }
|
| + return prefix;
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| * Parse a await expression. Return the await expression that was parsed.
|
| *
|
| * This method assumes that the current token matches `_AWAIT`.
|
| @@ -1437,7 +1036,7 @@ class Parser {
|
| bool progress = true;
|
| while (progress) {
|
| progress = false;
|
| - Expression selector = _parseAssignableSelector(expression, true);
|
| + Expression selector = parseAssignableSelector(expression, true);
|
| if (!identical(selector, expression)) {
|
| expression = selector;
|
| progress = true;
|
| @@ -1611,7 +1210,7 @@ class Parser {
|
| */
|
| ClassMember parseClassMember(String className) {
|
| CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
|
| - Modifiers modifiers = _parseModifiers();
|
| + Modifiers modifiers = parseModifiers();
|
| Keyword keyword = _currentToken.keyword;
|
| if (keyword == Keyword.VOID) {
|
| TypeName returnType =
|
| @@ -1621,11 +1220,11 @@ class Parser {
|
| bool isFollowedByIdentifier = _tokenMatchesIdentifier(next);
|
| if (keyword == Keyword.GET && isFollowedByIdentifier) {
|
| _validateModifiersForGetterOrSetterOrMethod(modifiers);
|
| - return _parseGetter(commentAndMetadata, modifiers.externalKeyword,
|
| + return parseGetter(commentAndMetadata, modifiers.externalKeyword,
|
| modifiers.staticKeyword, returnType);
|
| } else if (keyword == Keyword.SET && isFollowedByIdentifier) {
|
| _validateModifiersForGetterOrSetterOrMethod(modifiers);
|
| - return _parseSetter(commentAndMetadata, modifiers.externalKeyword,
|
| + return parseSetter(commentAndMetadata, modifiers.externalKeyword,
|
| modifiers.staticKeyword, returnType);
|
| } else if (keyword == Keyword.OPERATOR && _isOperator(next)) {
|
| _validateModifiersForOperator(modifiers);
|
| @@ -1655,7 +1254,7 @@ class Parser {
|
| // We appear to have a variable declaration with a type of "void".
|
| //
|
| _reportErrorForNode(ParserErrorCode.VOID_VARIABLE, returnType);
|
| - return _parseInitializedIdentifierList(
|
| + return parseInitializedIdentifierList(
|
| commentAndMetadata,
|
| modifiers.staticKeyword,
|
| _validateModifiersForField(modifiers),
|
| @@ -1680,11 +1279,11 @@ class Parser {
|
| bool isFollowedByIdentifier = _tokenMatchesIdentifier(next);
|
| if (keyword == Keyword.GET && isFollowedByIdentifier) {
|
| _validateModifiersForGetterOrSetterOrMethod(modifiers);
|
| - return _parseGetter(commentAndMetadata, modifiers.externalKeyword,
|
| + return parseGetter(commentAndMetadata, modifiers.externalKeyword,
|
| modifiers.staticKeyword, null);
|
| } else if (keyword == Keyword.SET && isFollowedByIdentifier) {
|
| _validateModifiersForGetterOrSetterOrMethod(modifiers);
|
| - return _parseSetter(commentAndMetadata, modifiers.externalKeyword,
|
| + return parseSetter(commentAndMetadata, modifiers.externalKeyword,
|
| modifiers.staticKeyword, null);
|
| } else if (keyword == Keyword.OPERATOR && _isOperator(next)) {
|
| _validateModifiersForOperator(modifiers);
|
| @@ -1820,7 +1419,7 @@ class Parser {
|
| _reportErrorForCurrentToken(
|
| ParserErrorCode.MISSING_CONST_FINAL_VAR_OR_TYPE);
|
| }
|
| - return _parseInitializedIdentifierList(commentAndMetadata,
|
| + return parseInitializedIdentifierList(commentAndMetadata,
|
| modifiers.staticKeyword, _validateModifiersForField(modifiers), null);
|
| } else if (keyword == Keyword.TYPEDEF) {
|
| _reportErrorForCurrentToken(ParserErrorCode.TYPEDEF_IN_CLASS);
|
| @@ -1841,11 +1440,11 @@ class Parser {
|
| isFollowedByIdentifier = _tokenMatchesIdentifier(next);
|
| if (keyword == Keyword.GET && isFollowedByIdentifier) {
|
| _validateModifiersForGetterOrSetterOrMethod(modifiers);
|
| - return _parseGetter(commentAndMetadata, modifiers.externalKeyword,
|
| + return parseGetter(commentAndMetadata, modifiers.externalKeyword,
|
| modifiers.staticKeyword, type);
|
| } else if (keyword == Keyword.SET && isFollowedByIdentifier) {
|
| _validateModifiersForGetterOrSetterOrMethod(modifiers);
|
| - return _parseSetter(commentAndMetadata, modifiers.externalKeyword,
|
| + return parseSetter(commentAndMetadata, modifiers.externalKeyword,
|
| modifiers.staticKeyword, type);
|
| } else if (keyword == Keyword.OPERATOR && _isOperator(next)) {
|
| _validateModifiersForOperator(modifiers);
|
| @@ -1858,7 +1457,7 @@ class Parser {
|
| // class. At this point it consists of a type name, so we'll treat it as
|
| // a field declaration with a missing field name and semicolon.
|
| //
|
| - return _parseInitializedIdentifierList(
|
| + return parseInitializedIdentifierList(
|
| commentAndMetadata,
|
| modifiers.staticKeyword,
|
| _validateModifiersForField(modifiers),
|
| @@ -1882,7 +1481,7 @@ class Parser {
|
| ParserErrorCode.EXPECTED_CLASS_MEMBER, _currentToken);
|
| try {
|
| _lockErrorListener();
|
| - return _parseInitializedIdentifierList(
|
| + return parseInitializedIdentifierList(
|
| commentAndMetadata,
|
| modifiers.staticKeyword,
|
| _validateModifiersForField(modifiers),
|
| @@ -1927,10 +1526,10 @@ class Parser {
|
| _reportErrorForCurrentToken(ParserErrorCode.MISSING_GET);
|
| _currentToken = _injectToken(
|
| new Parser_SyntheticKeywordToken(Keyword.GET, _currentToken.offset));
|
| - return _parseGetter(commentAndMetadata, modifiers.externalKeyword,
|
| + return parseGetter(commentAndMetadata, modifiers.externalKeyword,
|
| modifiers.staticKeyword, type);
|
| }
|
| - return _parseInitializedIdentifierList(commentAndMetadata,
|
| + return parseInitializedIdentifierList(commentAndMetadata,
|
| modifiers.staticKeyword, _validateModifiersForField(modifiers), type);
|
| }
|
|
|
| @@ -1970,9 +1569,9 @@ class Parser {
|
| */
|
| Combinator parseCombinator() {
|
| if (_matchesString(_SHOW)) {
|
| - return new ShowCombinator(getAndAdvance(), _parseIdentifierList());
|
| + return new ShowCombinator(getAndAdvance(), parseIdentifierList());
|
| } else if (_matchesString(_HIDE)) {
|
| - return new HideCombinator(getAndAdvance(), _parseIdentifierList());
|
| + return new HideCombinator(getAndAdvance(), parseIdentifierList());
|
| }
|
| return null;
|
| }
|
| @@ -2134,6 +1733,90 @@ class Parser {
|
| }
|
|
|
| /**
|
| + * Parse all of the comment references occurring in the given array of
|
| + * documentation comments. The [tokens] are the comment tokens representing
|
| + * the documentation comments to be parsed. Return the comment references that
|
| + * were parsed.
|
| + *
|
| + * commentReference ::=
|
| + * '[' 'new'? qualified ']' libraryReference?
|
| + *
|
| + * libraryReference ::=
|
| + * '(' stringLiteral ')'
|
| + */
|
| + List<CommentReference> parseCommentReferences(
|
| + List<DocumentationCommentToken> tokens) {
|
| + List<CommentReference> references = <CommentReference>[];
|
| + bool isInGitHubCodeBlock = false;
|
| + for (DocumentationCommentToken token in tokens) {
|
| + String comment = token.lexeme;
|
| + // Skip GitHub code blocks.
|
| + // https://help.github.com/articles/creating-and-highlighting-code-blocks/
|
| + if (tokens.length != 1) {
|
| + if (comment.indexOf('```') != -1) {
|
| + isInGitHubCodeBlock = !isInGitHubCodeBlock;
|
| + }
|
| + if (isInGitHubCodeBlock) {
|
| + continue;
|
| + }
|
| + }
|
| + // Remove GitHub include code.
|
| + comment = _removeGitHubInlineCode(comment);
|
| + // Find references.
|
| + int length = comment.length;
|
| + List<List<int>> codeBlockRanges = _getCodeBlockRanges(comment);
|
| + int leftIndex = comment.indexOf('[');
|
| + while (leftIndex >= 0 && leftIndex + 1 < length) {
|
| + List<int> range = _findRange(codeBlockRanges, leftIndex);
|
| + if (range == null) {
|
| + int nameOffset = token.offset + leftIndex + 1;
|
| + int rightIndex = comment.indexOf(']', leftIndex);
|
| + if (rightIndex >= 0) {
|
| + int firstChar = comment.codeUnitAt(leftIndex + 1);
|
| + if (firstChar != 0x27 && firstChar != 0x22) {
|
| + if (_isLinkText(comment, rightIndex)) {
|
| + // TODO(brianwilkerson) Handle the case where there's a library
|
| + // URI in the link text.
|
| + } else {
|
| + CommentReference reference = parseCommentReference(
|
| + comment.substring(leftIndex + 1, rightIndex), nameOffset);
|
| + if (reference != null) {
|
| + references.add(reference);
|
| + token.references.add(reference.beginToken);
|
| + }
|
| + }
|
| + }
|
| + } else {
|
| + // terminating ']' is not typed yet
|
| + int charAfterLeft = comment.codeUnitAt(leftIndex + 1);
|
| + Token nameToken;
|
| + if (Character.isLetterOrDigit(charAfterLeft)) {
|
| + int nameEnd = StringUtilities.indexOfFirstNotLetterDigit(
|
| + comment, leftIndex + 1);
|
| + String name = comment.substring(leftIndex + 1, nameEnd);
|
| + nameToken =
|
| + new StringToken(TokenType.IDENTIFIER, name, nameOffset);
|
| + } else {
|
| + nameToken = new SyntheticStringToken(
|
| + TokenType.IDENTIFIER, '', nameOffset);
|
| + }
|
| + nameToken.setNext(new SimpleToken(TokenType.EOF, nameToken.end));
|
| + references.add(
|
| + new CommentReference(null, new SimpleIdentifier(nameToken)));
|
| + token.references.add(nameToken);
|
| + // next character
|
| + rightIndex = leftIndex + 1;
|
| + }
|
| + leftIndex = comment.indexOf('[', rightIndex);
|
| + } else {
|
| + leftIndex = comment.indexOf('[', range[1]);
|
| + }
|
| + }
|
| + }
|
| + return references;
|
| + }
|
| +
|
| + /**
|
| * Parse a compilation unit, starting with the given [token]. Return the
|
| * compilation unit that was parsed.
|
| */
|
| @@ -2195,13 +1878,13 @@ class Parser {
|
| _reportErrorForCurrentToken(
|
| ParserErrorCode.IMPORT_DIRECTIVE_AFTER_PART_DIRECTIVE);
|
| }
|
| - return _parseImportDirective(commentAndMetadata);
|
| + return parseImportDirective(commentAndMetadata);
|
| } else if (keyword == Keyword.EXPORT) {
|
| if (partDirectiveFound) {
|
| _reportErrorForCurrentToken(
|
| ParserErrorCode.EXPORT_DIRECTIVE_AFTER_PART_DIRECTIVE);
|
| }
|
| - return _parseExportDirective(commentAndMetadata);
|
| + return parseExportDirective(commentAndMetadata);
|
| } else if (keyword == Keyword.LIBRARY) {
|
| if (libraryDirectiveFound) {
|
| _reportErrorForCurrentToken(
|
| @@ -2213,7 +1896,7 @@ class Parser {
|
| }
|
| libraryDirectiveFound = true;
|
| }
|
| - return _parseLibraryDirective(commentAndMetadata);
|
| + return parseLibraryDirective(commentAndMetadata);
|
| } else if (keyword == Keyword.PART) {
|
| if (_tokenMatchesString(_peek(), _OF)) {
|
| partOfDirectiveFound = true;
|
| @@ -2317,7 +2000,7 @@ class Parser {
|
| */
|
| CompilationUnitMember parseCompilationUnitMember(
|
| CommentAndMetadata commentAndMetadata) {
|
| - Modifiers modifiers = _parseModifiers();
|
| + Modifiers modifiers = parseModifiers();
|
| Keyword keyword = _currentToken.keyword;
|
| if (keyword == Keyword.CLASS) {
|
| return parseClassDeclaration(
|
| @@ -2330,7 +2013,7 @@ class Parser {
|
| nextType != TokenType.LT &&
|
| nextType != TokenType.OPEN_PAREN) {
|
| _validateModifiersForTypedef(modifiers);
|
| - return _parseTypeAlias(commentAndMetadata);
|
| + return parseTypeAlias(commentAndMetadata);
|
| } else if (keyword == Keyword.ENUM) {
|
| _validateModifiersForEnum(modifiers);
|
| return parseEnumDeclaration(commentAndMetadata);
|
| @@ -2342,7 +2025,7 @@ class Parser {
|
| if ((keyword == Keyword.GET || keyword == Keyword.SET) &&
|
| _tokenMatchesIdentifier(next)) {
|
| _validateModifiersForTopLevelFunction(modifiers);
|
| - return _parseFunctionDeclaration(
|
| + return parseFunctionDeclaration(
|
| commentAndMetadata, modifiers.externalKeyword, returnType);
|
| } else if (keyword == Keyword.OPERATOR && _isOperator(next)) {
|
| _reportErrorForToken(ParserErrorCode.TOP_LEVEL_OPERATOR, _currentToken);
|
| @@ -2359,7 +2042,7 @@ class Parser {
|
| TokenType.LT
|
| ])) {
|
| _validateModifiersForTopLevelFunction(modifiers);
|
| - return _parseFunctionDeclaration(
|
| + return parseFunctionDeclaration(
|
| commentAndMetadata, modifiers.externalKeyword, returnType);
|
| } else {
|
| //
|
| @@ -2390,7 +2073,7 @@ class Parser {
|
| } else if ((keyword == Keyword.GET || keyword == Keyword.SET) &&
|
| _tokenMatchesIdentifier(next)) {
|
| _validateModifiersForTopLevelFunction(modifiers);
|
| - return _parseFunctionDeclaration(
|
| + return parseFunctionDeclaration(
|
| commentAndMetadata, modifiers.externalKeyword, null);
|
| } else if (keyword == Keyword.OPERATOR && _isOperator(next)) {
|
| _reportErrorForToken(ParserErrorCode.TOP_LEVEL_OPERATOR, _currentToken);
|
| @@ -2424,12 +2107,12 @@ class Parser {
|
| _reportErrorForToken(ParserErrorCode.EXPECTED_EXECUTABLE, _currentToken);
|
| return null;
|
| } else if (_isPeekGenericTypeParametersAndOpenParen()) {
|
| - return _parseFunctionDeclaration(
|
| + return parseFunctionDeclaration(
|
| commentAndMetadata, modifiers.externalKeyword, null);
|
| } else if (_tokenMatches(next, TokenType.OPEN_PAREN)) {
|
| TypeName returnType = _parseOptionalTypeNameComment();
|
| _validateModifiersForTopLevelFunction(modifiers);
|
| - return _parseFunctionDeclaration(
|
| + return parseFunctionDeclaration(
|
| commentAndMetadata, modifiers.externalKeyword, returnType);
|
| } else if (next.matchesAny(const <TokenType>[
|
| TokenType.EQ,
|
| @@ -2455,7 +2138,7 @@ class Parser {
|
| if ((keyword == Keyword.GET || keyword == Keyword.SET) &&
|
| _tokenMatchesIdentifier(next)) {
|
| _validateModifiersForTopLevelFunction(modifiers);
|
| - return _parseFunctionDeclaration(
|
| + return parseFunctionDeclaration(
|
| commentAndMetadata, modifiers.externalKeyword, returnType);
|
| } else if (keyword == Keyword.OPERATOR && _isOperator(next)) {
|
| _reportErrorForToken(ParserErrorCode.TOP_LEVEL_OPERATOR, _currentToken);
|
| @@ -2496,7 +2179,7 @@ class Parser {
|
| TokenType.LT
|
| ])) {
|
| _validateModifiersForTopLevelFunction(modifiers);
|
| - return _parseFunctionDeclaration(
|
| + return parseFunctionDeclaration(
|
| commentAndMetadata, modifiers.externalKeyword, returnType);
|
| }
|
| return new TopLevelVariableDeclaration(
|
| @@ -2578,11 +2261,66 @@ class Parser {
|
| return parseListOrMapLiteral(keyword);
|
| } else if (type == TokenType.OPEN_SQUARE_BRACKET ||
|
| type == TokenType.INDEX) {
|
| - return _parseListLiteral(keyword, null);
|
| + return parseListLiteral(keyword, null);
|
| } else if (type == TokenType.OPEN_CURLY_BRACKET) {
|
| - return _parseMapLiteral(keyword, null);
|
| + return parseMapLiteral(keyword, null);
|
| + }
|
| + return parseInstanceCreationExpression(keyword);
|
| + }
|
| +
|
| + /**
|
| + * Parse a field initializer within a constructor. The flag [hasThis] should
|
| + * be true if the current token is `this`. Return the field initializer that
|
| + * was parsed.
|
| + *
|
| + * fieldInitializer:
|
| + * ('this' '.')? identifier '=' conditionalExpression cascadeSection*
|
| + */
|
| + ConstructorFieldInitializer parseConstructorFieldInitializer(bool hasThis) {
|
| + Token keywordToken = null;
|
| + Token period = null;
|
| + if (hasThis) {
|
| + keywordToken = getAndAdvance();
|
| + period = _expect(TokenType.PERIOD);
|
| + }
|
| + SimpleIdentifier fieldName = parseSimpleIdentifier();
|
| + Token equals = null;
|
| + TokenType type = _currentToken.type;
|
| + if (type == TokenType.EQ) {
|
| + equals = getAndAdvance();
|
| + } else {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.MISSING_ASSIGNMENT_IN_INITIALIZER);
|
| + Keyword keyword = _currentToken.keyword;
|
| + if (keyword != Keyword.THIS &&
|
| + keyword != Keyword.SUPER &&
|
| + type != TokenType.OPEN_CURLY_BRACKET &&
|
| + type != TokenType.FUNCTION) {
|
| + equals = _createSyntheticToken(TokenType.EQ);
|
| + } else {
|
| + return new ConstructorFieldInitializer(keywordToken, period, fieldName,
|
| + _createSyntheticToken(TokenType.EQ), createSyntheticIdentifier());
|
| + }
|
| + }
|
| + bool wasInInitializer = _inInitializer;
|
| + _inInitializer = true;
|
| + try {
|
| + Expression expression = parseConditionalExpression();
|
| + if (_matches(TokenType.PERIOD_PERIOD)) {
|
| + List<Expression> cascadeSections = <Expression>[];
|
| + do {
|
| + Expression section = parseCascadeSection();
|
| + if (section != null) {
|
| + cascadeSections.add(section);
|
| + }
|
| + } while (_matches(TokenType.PERIOD_PERIOD));
|
| + expression = new CascadeExpression(expression, cascadeSections);
|
| + }
|
| + return new ConstructorFieldInitializer(
|
| + keywordToken, period, fieldName, equals, expression);
|
| + } finally {
|
| + _inInitializer = wasInInitializer;
|
| }
|
| - return _parseInstanceCreationExpression(keyword);
|
| }
|
|
|
| /**
|
| @@ -2630,6 +2368,33 @@ class Parser {
|
| }
|
|
|
| /**
|
| + * Parse a directive. The [commentAndMetadata] is the metadata to be
|
| + * associated with the directive. Return the directive that was parsed.
|
| + *
|
| + * directive ::=
|
| + * exportDirective
|
| + * | libraryDirective
|
| + * | importDirective
|
| + * | partDirective
|
| + */
|
| + Directive parseDirective(CommentAndMetadata commentAndMetadata) {
|
| + if (_matchesKeyword(Keyword.IMPORT)) {
|
| + return parseImportDirective(commentAndMetadata);
|
| + } else if (_matchesKeyword(Keyword.EXPORT)) {
|
| + return parseExportDirective(commentAndMetadata);
|
| + } else if (_matchesKeyword(Keyword.LIBRARY)) {
|
| + return parseLibraryDirective(commentAndMetadata);
|
| + } else if (_matchesKeyword(Keyword.PART)) {
|
| + return parsePartOrPartOfDirective(commentAndMetadata);
|
| + } else {
|
| + // Internal error: this method should not have been invoked if the current
|
| + // token was something other than one of the above.
|
| + throw new StateError(
|
| + "parseDirective invoked in an invalid state; currentToken = $_currentToken");
|
| + }
|
| + }
|
| +
|
| + /**
|
| * Parse the script tag and directives in a compilation unit, starting with
|
| * the given [token], until the first non-directive is encountered. The
|
| * remainder of the compilation unit will not be parsed. Specifically, if
|
| @@ -2666,7 +2431,7 @@ class Parser {
|
| type != TokenType.PERIOD &&
|
| type != TokenType.LT &&
|
| type != TokenType.OPEN_PAREN) {
|
| - directives.add(_parseDirective(commentAndMetadata));
|
| + directives.add(parseDirective(commentAndMetadata));
|
| } else if (_matches(TokenType.SEMICOLON)) {
|
| _advance();
|
| } else {
|
| @@ -2694,7 +2459,7 @@ class Parser {
|
| if (tokens == null) {
|
| return null;
|
| }
|
| - List<CommentReference> references = _parseCommentReferences(tokens);
|
| + List<CommentReference> references = parseCommentReferences(tokens);
|
| return Comment.createDocumentationCommentWithReferences(tokens, references);
|
| }
|
|
|
| @@ -2829,27 +2594,81 @@ class Parser {
|
| }
|
|
|
| /**
|
| - * Parse an expression, starting with the given [token]. Return the expression
|
| - * that was parsed, or `null` if the tokens do not represent a recognizable
|
| - * expression.
|
| + * Parse an equality expression. Return the equality expression that was
|
| + * parsed.
|
| + *
|
| + * equalityExpression ::=
|
| + * relationalExpression (equalityOperator relationalExpression)?
|
| + * | 'super' equalityOperator relationalExpression
|
| */
|
| - Expression parseExpression(Token token) {
|
| - _currentToken = token;
|
| - return parseExpression2();
|
| + Expression parseEqualityExpression() {
|
| + Expression expression;
|
| + if (_currentToken.keyword == Keyword.SUPER &&
|
| + _currentToken.next.type.isEqualityOperator) {
|
| + expression = new SuperExpression(getAndAdvance());
|
| + } else {
|
| + expression = parseRelationalExpression();
|
| + }
|
| + bool leftEqualityExpression = false;
|
| + while (_currentToken.type.isEqualityOperator) {
|
| + if (leftEqualityExpression) {
|
| + _reportErrorForNode(
|
| + ParserErrorCode.EQUALITY_CANNOT_BE_EQUALITY_OPERAND, expression);
|
| + }
|
| + expression = new BinaryExpression(
|
| + expression, getAndAdvance(), parseRelationalExpression());
|
| + leftEqualityExpression = true;
|
| + }
|
| + return expression;
|
| }
|
|
|
| /**
|
| - * Parse an expression that might contain a cascade. Return the expression
|
| - * that was parsed.
|
| + * Parse an export directive. The [commentAndMetadata] is the metadata to be
|
| + * associated with the directive. Return the export directive that was parsed.
|
| *
|
| - * expression ::=
|
| - * assignableExpression assignmentOperator expression
|
| - * | conditionalExpression cascadeSection*
|
| - * | throwExpression
|
| + * This method assumes that the current token matches `Keyword.EXPORT`.
|
| + *
|
| + * exportDirective ::=
|
| + * metadata 'export' stringLiteral configuration* combinator*';'
|
| */
|
| - Expression parseExpression2() {
|
| - Keyword keyword = _currentToken.keyword;
|
| - if (keyword == Keyword.THROW) {
|
| + ExportDirective parseExportDirective(CommentAndMetadata commentAndMetadata) {
|
| + Token exportKeyword = getAndAdvance();
|
| + StringLiteral libraryUri = _parseUri();
|
| + List<Configuration> configurations = _parseConfigurations();
|
| + List<Combinator> combinators = parseCombinators();
|
| + Token semicolon = _expect(TokenType.SEMICOLON);
|
| + return new ExportDirective(
|
| + commentAndMetadata.comment,
|
| + commentAndMetadata.metadata,
|
| + exportKeyword,
|
| + libraryUri,
|
| + configurations,
|
| + combinators,
|
| + semicolon);
|
| + }
|
| +
|
| + /**
|
| + * Parse an expression, starting with the given [token]. Return the expression
|
| + * that was parsed, or `null` if the tokens do not represent a recognizable
|
| + * expression.
|
| + */
|
| + Expression parseExpression(Token token) {
|
| + _currentToken = token;
|
| + return parseExpression2();
|
| + }
|
| +
|
| + /**
|
| + * Parse an expression that might contain a cascade. Return the expression
|
| + * that was parsed.
|
| + *
|
| + * expression ::=
|
| + * assignableExpression assignmentOperator expression
|
| + * | conditionalExpression cascadeSection*
|
| + * | throwExpression
|
| + */
|
| + Expression parseExpression2() {
|
| + Keyword keyword = _currentToken.keyword;
|
| + if (keyword == Keyword.THROW) {
|
| return parseThrowExpression();
|
| } else if (keyword == Keyword.RETHROW) {
|
| // TODO(brianwilkerson) Rethrow is a statement again.
|
| @@ -2985,6 +2804,52 @@ class Parser {
|
| }
|
|
|
| /**
|
| + * Parse a formal parameter. At most one of `isOptional` and `isNamed` can be
|
| + * `true`. The [kind] is the kind of parameter being expected based on the
|
| + * presence or absence of group delimiters. Return the formal parameter that
|
| + * was parsed.
|
| + *
|
| + * defaultFormalParameter ::=
|
| + * normalFormalParameter ('=' expression)?
|
| + *
|
| + * defaultNamedParameter ::=
|
| + * normalFormalParameter (':' expression)?
|
| + */
|
| + FormalParameter parseFormalParameter(ParameterKind kind) {
|
| + NormalFormalParameter parameter = parseNormalFormalParameter();
|
| + TokenType type = _currentToken.type;
|
| + if (type == TokenType.EQ) {
|
| + Token separator = getAndAdvance();
|
| + Expression defaultValue = parseExpression2();
|
| + if (kind == ParameterKind.NAMED) {
|
| + _reportErrorForToken(
|
| + ParserErrorCode.WRONG_SEPARATOR_FOR_NAMED_PARAMETER, separator);
|
| + } else if (kind == ParameterKind.REQUIRED) {
|
| + _reportErrorForNode(
|
| + ParserErrorCode.POSITIONAL_PARAMETER_OUTSIDE_GROUP, parameter);
|
| + }
|
| + return new DefaultFormalParameter(
|
| + parameter, kind, separator, defaultValue);
|
| + } else if (type == TokenType.COLON) {
|
| + Token separator = getAndAdvance();
|
| + Expression defaultValue = parseExpression2();
|
| + if (kind == ParameterKind.POSITIONAL) {
|
| + _reportErrorForToken(
|
| + ParserErrorCode.WRONG_SEPARATOR_FOR_POSITIONAL_PARAMETER,
|
| + separator);
|
| + } else if (kind == ParameterKind.REQUIRED) {
|
| + _reportErrorForNode(
|
| + ParserErrorCode.NAMED_PARAMETER_OUTSIDE_GROUP, parameter);
|
| + }
|
| + return new DefaultFormalParameter(
|
| + parameter, kind, separator, defaultValue);
|
| + } else if (kind != ParameterKind.REQUIRED) {
|
| + return new DefaultFormalParameter(parameter, kind, null, null);
|
| + }
|
| + return parameter;
|
| + }
|
| +
|
| + /**
|
| * Parse a list of formal parameters. Return the formal parameters that were
|
| * parsed.
|
| *
|
| @@ -3289,6 +3154,82 @@ class Parser {
|
| }
|
|
|
| /**
|
| + * Parse a function declaration. The [commentAndMetadata] is the documentation
|
| + * comment and metadata to be associated with the declaration. The
|
| + * [externalKeyword] is the 'external' keyword, or `null` if the function is
|
| + * not external. The [returnType] is the return type, or `null` if there is no
|
| + * return type. The [isStatement] is `true` if the function declaration is
|
| + * being parsed as a statement. Return the function declaration that was
|
| + * parsed.
|
| + *
|
| + * functionDeclaration ::=
|
| + * functionSignature functionBody
|
| + * | returnType? getOrSet identifier formalParameterList functionBody
|
| + */
|
| + FunctionDeclaration parseFunctionDeclaration(
|
| + CommentAndMetadata commentAndMetadata,
|
| + Token externalKeyword,
|
| + TypeName returnType) {
|
| + Token keywordToken = null;
|
| + bool isGetter = false;
|
| + Keyword keyword = _currentToken.keyword;
|
| + SimpleIdentifier name = null;
|
| + if (keyword == Keyword.GET) {
|
| + keywordToken = getAndAdvance();
|
| + isGetter = true;
|
| + } else if (keyword == Keyword.SET) {
|
| + keywordToken = getAndAdvance();
|
| + }
|
| + if (keywordToken != null && _matches(TokenType.OPEN_PAREN)) {
|
| + name = new SimpleIdentifier(keywordToken, isDeclaration: true);
|
| + keywordToken = null;
|
| + isGetter = false;
|
| + } else {
|
| + name = parseSimpleIdentifier(isDeclaration: true);
|
| + }
|
| + TypeParameterList typeParameters = _parseGenericMethodTypeParameters();
|
| + FormalParameterList parameters = null;
|
| + if (!isGetter) {
|
| + if (_matches(TokenType.OPEN_PAREN)) {
|
| + parameters = _parseFormalParameterListUnchecked();
|
| + _validateFormalParameterList(parameters);
|
| + } else {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.MISSING_FUNCTION_PARAMETERS);
|
| + parameters = new FormalParameterList(
|
| + _createSyntheticToken(TokenType.OPEN_PAREN),
|
| + null,
|
| + null,
|
| + null,
|
| + _createSyntheticToken(TokenType.CLOSE_PAREN));
|
| + }
|
| + } else if (_matches(TokenType.OPEN_PAREN)) {
|
| + _reportErrorForCurrentToken(ParserErrorCode.GETTER_WITH_PARAMETERS);
|
| + _parseFormalParameterListUnchecked();
|
| + }
|
| + FunctionBody body;
|
| + if (externalKeyword == null) {
|
| + body = parseFunctionBody(
|
| + false, ParserErrorCode.MISSING_FUNCTION_BODY, false);
|
| + } else {
|
| + body = new EmptyFunctionBody(_expect(TokenType.SEMICOLON));
|
| + }
|
| +// if (!isStatement && matches(TokenType.SEMICOLON)) {
|
| +// // TODO(brianwilkerson) Improve this error message.
|
| +// reportError(ParserErrorCode.UNEXPECTED_TOKEN, currentToken.getLexeme());
|
| +// advance();
|
| +// }
|
| + return new FunctionDeclaration(
|
| + commentAndMetadata.comment,
|
| + commentAndMetadata.metadata,
|
| + externalKeyword,
|
| + returnType,
|
| + keywordToken,
|
| + name,
|
| + new FunctionExpression(typeParameters, parameters, body));
|
| + }
|
| +
|
| + /**
|
| * Parse a function declaration statement. Return the function declaration
|
| * statement that was parsed.
|
| *
|
| @@ -3296,7 +3237,7 @@ class Parser {
|
| * functionSignature functionBody
|
| */
|
| Statement parseFunctionDeclarationStatement() {
|
| - Modifiers modifiers = _parseModifiers();
|
| + Modifiers modifiers = parseModifiers();
|
| _validateModifiersForFunctionDeclarationStatement(modifiers);
|
| return _parseFunctionDeclarationStatementAfterReturnType(
|
| parseCommentAndMetadata(), _parseOptionalReturnType());
|
| @@ -3319,6 +3260,70 @@ class Parser {
|
| }
|
|
|
| /**
|
| + * Parse a getter. The [commentAndMetadata] is the documentation comment and
|
| + * metadata to be associated with the declaration. The externalKeyword] is the
|
| + * 'external' token. The staticKeyword] is the static keyword, or `null` if
|
| + * the getter is not static. The [returnType] the return type that has already
|
| + * been parsed, or `null` if there was no return type. Return the getter that
|
| + * was parsed.
|
| + *
|
| + * This method assumes that the current token matches `Keyword.GET`.
|
| + *
|
| + * getter ::=
|
| + * getterSignature functionBody?
|
| + *
|
| + * getterSignature ::=
|
| + * 'external'? 'static'? returnType? 'get' identifier
|
| + */
|
| + MethodDeclaration parseGetter(CommentAndMetadata commentAndMetadata,
|
| + Token externalKeyword, Token staticKeyword, TypeName returnType) {
|
| + Token propertyKeyword = getAndAdvance();
|
| + SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
|
| + if (_matches(TokenType.OPEN_PAREN) &&
|
| + _tokenMatches(_peek(), TokenType.CLOSE_PAREN)) {
|
| + _reportErrorForCurrentToken(ParserErrorCode.GETTER_WITH_PARAMETERS);
|
| + _advance();
|
| + _advance();
|
| + }
|
| + FunctionBody body = parseFunctionBody(
|
| + externalKeyword != null || staticKeyword == null,
|
| + ParserErrorCode.STATIC_GETTER_WITHOUT_BODY,
|
| + false);
|
| + if (externalKeyword != null && body is! EmptyFunctionBody) {
|
| + _reportErrorForCurrentToken(ParserErrorCode.EXTERNAL_GETTER_WITH_BODY);
|
| + }
|
| + return new MethodDeclaration(
|
| + commentAndMetadata.comment,
|
| + commentAndMetadata.metadata,
|
| + externalKeyword,
|
| + staticKeyword,
|
| + returnType,
|
| + propertyKeyword,
|
| + null,
|
| + name,
|
| + null,
|
| + null,
|
| + body);
|
| + }
|
| +
|
| + /**
|
| + * Parse a list of identifiers. Return the list of identifiers that were
|
| + * parsed.
|
| + *
|
| + * identifierList ::=
|
| + * identifier (',' identifier)*
|
| + */
|
| + List<SimpleIdentifier> parseIdentifierList() {
|
| + List<SimpleIdentifier> identifiers = <SimpleIdentifier>[
|
| + parseSimpleIdentifier()
|
| + ];
|
| + while (_optional(TokenType.COMMA)) {
|
| + identifiers.add(parseSimpleIdentifier());
|
| + }
|
| + return identifiers;
|
| + }
|
| +
|
| + /**
|
| * Parse an if-null expression. Return the if-null expression that was
|
| * parsed.
|
| *
|
| @@ -3376,34 +3381,211 @@ class Parser {
|
| }
|
|
|
| /**
|
| - * Parse a label. Return the label that was parsed.
|
| - *
|
| - * This method assumes that the current token matches an identifier and that
|
| - * the following token matches `TokenType.COLON`.
|
| + * Parse an import directive. The [commentAndMetadata] is the metadata to be
|
| + * associated with the directive. Return the import directive that was parsed.
|
| *
|
| - * label ::=
|
| - * identifier ':'
|
| - */
|
| - Label parseLabel({bool isDeclaration: false}) {
|
| - SimpleIdentifier label =
|
| - _parseSimpleIdentifierUnchecked(isDeclaration: isDeclaration);
|
| - Token colon = getAndAdvance();
|
| - return new Label(label, colon);
|
| - }
|
| -
|
| - /**
|
| - * Parse a library identifier. Return the library identifier that was parsed.
|
| + * This method assumes that the current token matches `Keyword.IMPORT`.
|
| *
|
| - * libraryIdentifier ::=
|
| - * identifier ('.' identifier)*
|
| + * importDirective ::=
|
| + * metadata 'import' stringLiteral configuration* (deferred)? ('as' identifier)? combinator*';'
|
| */
|
| - LibraryIdentifier parseLibraryIdentifier() {
|
| - List<SimpleIdentifier> components = <SimpleIdentifier>[];
|
| - components.add(parseSimpleIdentifier());
|
| - while (_optional(TokenType.PERIOD)) {
|
| - components.add(parseSimpleIdentifier());
|
| + ImportDirective parseImportDirective(CommentAndMetadata commentAndMetadata) {
|
| + Token importKeyword = getAndAdvance();
|
| + StringLiteral libraryUri = _parseUri();
|
| + List<Configuration> configurations = _parseConfigurations();
|
| + Token deferredToken = null;
|
| + Token asToken = null;
|
| + SimpleIdentifier prefix = null;
|
| + if (_matchesKeyword(Keyword.DEFERRED)) {
|
| + deferredToken = getAndAdvance();
|
| + }
|
| + if (_matchesKeyword(Keyword.AS)) {
|
| + asToken = getAndAdvance();
|
| + prefix = parseSimpleIdentifier(isDeclaration: true);
|
| + } else if (deferredToken != null) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.MISSING_PREFIX_IN_DEFERRED_IMPORT);
|
| + } else if (!_matches(TokenType.SEMICOLON) &&
|
| + !_matchesString(_SHOW) &&
|
| + !_matchesString(_HIDE)) {
|
| + Token nextToken = _peek();
|
| + if (_tokenMatchesKeyword(nextToken, Keyword.AS) ||
|
| + _tokenMatchesString(nextToken, _SHOW) ||
|
| + _tokenMatchesString(nextToken, _HIDE)) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.UNEXPECTED_TOKEN, [_currentToken]);
|
| + _advance();
|
| + if (_matchesKeyword(Keyword.AS)) {
|
| + asToken = getAndAdvance();
|
| + prefix = parseSimpleIdentifier(isDeclaration: true);
|
| + }
|
| + }
|
| + }
|
| + List<Combinator> combinators = parseCombinators();
|
| + Token semicolon = _expect(TokenType.SEMICOLON);
|
| + return new ImportDirective(
|
| + commentAndMetadata.comment,
|
| + commentAndMetadata.metadata,
|
| + importKeyword,
|
| + libraryUri,
|
| + configurations,
|
| + deferredToken,
|
| + asToken,
|
| + prefix,
|
| + combinators,
|
| + semicolon);
|
| + }
|
| +
|
| + /**
|
| + * Parse a list of initialized identifiers. The [commentAndMetadata] is the
|
| + * documentation comment and metadata to be associated with the declaration.
|
| + * The [staticKeyword] is the static keyword, or `null` if the getter is not
|
| + * static. The [keyword] is the token representing the 'final', 'const' or
|
| + * 'var' keyword, or `null` if there is no keyword. The [type] is the type
|
| + * that has already been parsed, or `null` if 'var' was provided. Return the
|
| + * getter that was parsed.
|
| + *
|
| + * ?? ::=
|
| + * 'static'? ('var' | type) initializedIdentifierList ';'
|
| + * | 'final' type? initializedIdentifierList ';'
|
| + *
|
| + * initializedIdentifierList ::=
|
| + * initializedIdentifier (',' initializedIdentifier)*
|
| + *
|
| + * initializedIdentifier ::=
|
| + * identifier ('=' expression)?
|
| + */
|
| + FieldDeclaration parseInitializedIdentifierList(
|
| + CommentAndMetadata commentAndMetadata,
|
| + Token staticKeyword,
|
| + Token keyword,
|
| + TypeName type) {
|
| + VariableDeclarationList fieldList =
|
| + parseVariableDeclarationListAfterType(null, keyword, type);
|
| + return new FieldDeclaration(
|
| + commentAndMetadata.comment,
|
| + commentAndMetadata.metadata,
|
| + staticKeyword,
|
| + fieldList,
|
| + _expect(TokenType.SEMICOLON));
|
| + }
|
| +
|
| + /**
|
| + * Parse an instance creation expression. The [keyword] is the 'new' or
|
| + * 'const' keyword that introduces the expression. Return the instance
|
| + * creation expression that was parsed.
|
| + *
|
| + * instanceCreationExpression ::=
|
| + * ('new' | 'const') type ('.' identifier)? argumentList
|
| + */
|
| + InstanceCreationExpression parseInstanceCreationExpression(Token keyword) {
|
| + ConstructorName constructorName = parseConstructorName();
|
| + ArgumentList argumentList = _parseArgumentListChecked();
|
| + return new InstanceCreationExpression(
|
| + keyword, constructorName, argumentList);
|
| + }
|
| +
|
| + /**
|
| + * Parse a label. Return the label that was parsed.
|
| + *
|
| + * This method assumes that the current token matches an identifier and that
|
| + * the following token matches `TokenType.COLON`.
|
| + *
|
| + * label ::=
|
| + * identifier ':'
|
| + */
|
| + Label parseLabel({bool isDeclaration: false}) {
|
| + SimpleIdentifier label =
|
| + _parseSimpleIdentifierUnchecked(isDeclaration: isDeclaration);
|
| + Token colon = getAndAdvance();
|
| + return new Label(label, colon);
|
| + }
|
| +
|
| + /**
|
| + * Parse a library directive. The [commentAndMetadata] is the metadata to be
|
| + * associated with the directive. Return the library directive that was
|
| + * parsed.
|
| + *
|
| + * This method assumes that the current token matches `Keyword.LIBRARY`.
|
| + *
|
| + * libraryDirective ::=
|
| + * metadata 'library' identifier ';'
|
| + */
|
| + LibraryDirective parseLibraryDirective(
|
| + CommentAndMetadata commentAndMetadata) {
|
| + Token keyword = getAndAdvance();
|
| + LibraryIdentifier libraryName = _parseLibraryName(
|
| + ParserErrorCode.MISSING_NAME_IN_LIBRARY_DIRECTIVE, keyword);
|
| + Token semicolon = _expect(TokenType.SEMICOLON);
|
| + return new LibraryDirective(commentAndMetadata.comment,
|
| + commentAndMetadata.metadata, keyword, libraryName, semicolon);
|
| + }
|
| +
|
| + /**
|
| + * Parse a library identifier. Return the library identifier that was parsed.
|
| + *
|
| + * libraryIdentifier ::=
|
| + * identifier ('.' identifier)*
|
| + */
|
| + LibraryIdentifier parseLibraryIdentifier() {
|
| + List<SimpleIdentifier> components = <SimpleIdentifier>[];
|
| + components.add(parseSimpleIdentifier());
|
| + while (_optional(TokenType.PERIOD)) {
|
| + components.add(parseSimpleIdentifier());
|
| + }
|
| + return new LibraryIdentifier(components);
|
| + }
|
| +
|
| + /**
|
| + * Parse a list literal. The [modifier] is the 'const' modifier appearing
|
| + * before the literal, or `null` if there is no modifier. The [typeArguments]
|
| + * is the type arguments appearing before the literal, or `null` if there are
|
| + * no type arguments. Return the list literal that was parsed.
|
| + *
|
| + * This method assumes that the current token matches either
|
| + * `TokenType.OPEN_SQUARE_BRACKET` or `TokenType.INDEX`.
|
| + *
|
| + * listLiteral ::=
|
| + * 'const'? typeArguments? '[' (expressionList ','?)? ']'
|
| + */
|
| + ListLiteral parseListLiteral(Token modifier, TypeArgumentList typeArguments) {
|
| + if (_matches(TokenType.INDEX)) {
|
| + // Split the token into two separate tokens.
|
| + BeginToken leftBracket = _createToken(
|
| + _currentToken, TokenType.OPEN_SQUARE_BRACKET,
|
| + isBegin: true);
|
| + Token rightBracket =
|
| + new Token(TokenType.CLOSE_SQUARE_BRACKET, _currentToken.offset + 1);
|
| + leftBracket.endToken = rightBracket;
|
| + rightBracket.setNext(_currentToken.next);
|
| + leftBracket.setNext(rightBracket);
|
| + _currentToken.previous.setNext(leftBracket);
|
| + _currentToken = _currentToken.next;
|
| + return new ListLiteral(
|
| + modifier, typeArguments, leftBracket, null, rightBracket);
|
| + }
|
| + Token leftBracket = getAndAdvance();
|
| + if (_matches(TokenType.CLOSE_SQUARE_BRACKET)) {
|
| + return new ListLiteral(
|
| + modifier, typeArguments, leftBracket, null, getAndAdvance());
|
| + }
|
| + bool wasInInitializer = _inInitializer;
|
| + _inInitializer = false;
|
| + try {
|
| + List<Expression> elements = <Expression>[parseExpression2()];
|
| + while (_optional(TokenType.COMMA)) {
|
| + if (_matches(TokenType.CLOSE_SQUARE_BRACKET)) {
|
| + return new ListLiteral(
|
| + modifier, typeArguments, leftBracket, elements, getAndAdvance());
|
| + }
|
| + elements.add(parseExpression2());
|
| + }
|
| + Token rightBracket = _expect(TokenType.CLOSE_SQUARE_BRACKET);
|
| + return new ListLiteral(
|
| + modifier, typeArguments, leftBracket, elements, rightBracket);
|
| + } finally {
|
| + _inInitializer = wasInInitializer;
|
| }
|
| - return new LibraryIdentifier(components);
|
| }
|
|
|
| /**
|
| @@ -3418,10 +3600,10 @@ class Parser {
|
| TypedLiteral parseListOrMapLiteral(Token modifier) {
|
| TypeArgumentList typeArguments = _parseOptionalTypeArguments();
|
| if (_matches(TokenType.OPEN_CURLY_BRACKET)) {
|
| - return _parseMapLiteral(modifier, typeArguments);
|
| + return parseMapLiteral(modifier, typeArguments);
|
| } else if (_matches(TokenType.OPEN_SQUARE_BRACKET) ||
|
| _matches(TokenType.INDEX)) {
|
| - return _parseListLiteral(modifier, typeArguments);
|
| + return parseListLiteral(modifier, typeArguments);
|
| }
|
| _reportErrorForCurrentToken(ParserErrorCode.EXPECTED_LIST_OR_MAP_LITERAL);
|
| return new ListLiteral(
|
| @@ -3433,6 +3615,22 @@ class Parser {
|
| }
|
|
|
| /**
|
| + * Parse a logical and expression. Return the logical and expression that was
|
| + * parsed.
|
| + *
|
| + * logicalAndExpression ::=
|
| + * equalityExpression ('&&' equalityExpression)*
|
| + */
|
| + Expression parseLogicalAndExpression() {
|
| + Expression expression = parseEqualityExpression();
|
| + while (_currentToken.type == TokenType.AMPERSAND_AMPERSAND) {
|
| + expression = new BinaryExpression(
|
| + expression, getAndAdvance(), parseEqualityExpression());
|
| + }
|
| + return expression;
|
| + }
|
| +
|
| + /**
|
| * Parse a logical or expression. Return the logical or expression that was
|
| * parsed.
|
| *
|
| @@ -3440,15 +3638,52 @@ class Parser {
|
| * logicalAndExpression ('||' logicalAndExpression)*
|
| */
|
| Expression parseLogicalOrExpression() {
|
| - Expression expression = _parseLogicalAndExpression();
|
| + Expression expression = parseLogicalAndExpression();
|
| while (_currentToken.type == TokenType.BAR_BAR) {
|
| expression = new BinaryExpression(
|
| - expression, getAndAdvance(), _parseLogicalAndExpression());
|
| + expression, getAndAdvance(), parseLogicalAndExpression());
|
| }
|
| return expression;
|
| }
|
|
|
| /**
|
| + * Parse a map literal. The [modifier] is the 'const' modifier appearing
|
| + * before the literal, or `null` if there is no modifier. The [typeArguments]
|
| + * is the type arguments that were declared, or `null` if there are no type
|
| + * arguments. Return the map literal that was parsed.
|
| + *
|
| + * This method assumes that the current token matches
|
| + * `TokenType.OPEN_CURLY_BRACKET`.
|
| + *
|
| + * mapLiteral ::=
|
| + * 'const'? typeArguments? '{' (mapLiteralEntry (',' mapLiteralEntry)* ','?)? '}'
|
| + */
|
| + MapLiteral parseMapLiteral(Token modifier, TypeArgumentList typeArguments) {
|
| + Token leftBracket = getAndAdvance();
|
| + if (_matches(TokenType.CLOSE_CURLY_BRACKET)) {
|
| + return new MapLiteral(
|
| + modifier, typeArguments, leftBracket, null, getAndAdvance());
|
| + }
|
| + bool wasInInitializer = _inInitializer;
|
| + _inInitializer = false;
|
| + try {
|
| + List<MapLiteralEntry> entries = <MapLiteralEntry>[parseMapLiteralEntry()];
|
| + while (_optional(TokenType.COMMA)) {
|
| + if (_matches(TokenType.CLOSE_CURLY_BRACKET)) {
|
| + return new MapLiteral(
|
| + modifier, typeArguments, leftBracket, entries, getAndAdvance());
|
| + }
|
| + entries.add(parseMapLiteralEntry());
|
| + }
|
| + Token rightBracket = _expect(TokenType.CLOSE_CURLY_BRACKET);
|
| + return new MapLiteral(
|
| + modifier, typeArguments, leftBracket, entries, rightBracket);
|
| + } finally {
|
| + _inInitializer = wasInInitializer;
|
| + }
|
| + }
|
| +
|
| + /**
|
| * Parse a map literal entry. Return the map literal entry that was parsed.
|
| *
|
| * mapLiteralEntry ::=
|
| @@ -3462,3577 +3697,3084 @@ class Parser {
|
| }
|
|
|
| /**
|
| - * Parse a multiplicative expression. Return the multiplicative expression
|
| - * that was parsed.
|
| + * Parse the modifiers preceding a declaration. This method allows the
|
| + * modifiers to appear in any order but does generate errors for duplicated
|
| + * modifiers. Checks for other problems, such as having the modifiers appear
|
| + * in the wrong order or specifying both 'const' and 'final', are reported in
|
| + * one of the methods whose name is prefixed with `validateModifiersFor`.
|
| + * Return the modifiers that were parsed.
|
| *
|
| - * multiplicativeExpression ::=
|
| - * unaryExpression (multiplicativeOperator unaryExpression)*
|
| - * | 'super' (multiplicativeOperator unaryExpression)+
|
| + * modifiers ::=
|
| + * ('abstract' | 'const' | 'external' | 'factory' | 'final' | 'static' | 'var')*
|
| */
|
| - Expression parseMultiplicativeExpression() {
|
| - Expression expression;
|
| - if (_currentToken.keyword == Keyword.SUPER &&
|
| - _currentToken.next.type.isMultiplicativeOperator) {
|
| - expression = new SuperExpression(getAndAdvance());
|
| - } else {
|
| - expression = parseUnaryExpression();
|
| - }
|
| - while (_currentToken.type.isMultiplicativeOperator) {
|
| - expression = new BinaryExpression(
|
| - expression, getAndAdvance(), parseUnaryExpression());
|
| - }
|
| - return expression;
|
| - }
|
| -
|
| - /**
|
| - * Parse a normal formal parameter. Return the normal formal parameter that
|
| - * was parsed.
|
| - *
|
| - * normalFormalParameter ::=
|
| - * functionSignature
|
| - * | fieldFormalParameter
|
| - * | simpleFormalParameter
|
| - *
|
| - * functionSignature:
|
| - * metadata returnType? identifier typeParameters? formalParameterList
|
| - *
|
| - * fieldFormalParameter ::=
|
| - * metadata finalConstVarOrType? 'this' '.' identifier
|
| - *
|
| - * simpleFormalParameter ::=
|
| - * declaredIdentifier
|
| - * | metadata identifier
|
| - */
|
| - NormalFormalParameter parseNormalFormalParameter() {
|
| - CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
|
| - FinalConstVarOrType holder = parseFinalConstVarOrType(true);
|
| - Token thisKeyword = null;
|
| - Token period = null;
|
| - if (_matchesKeyword(Keyword.THIS)) {
|
| - thisKeyword = getAndAdvance();
|
| - period = _expect(TokenType.PERIOD);
|
| - }
|
| - SimpleIdentifier identifier = parseSimpleIdentifier();
|
| - TypeParameterList typeParameters = _parseGenericMethodTypeParameters();
|
| - if (_matches(TokenType.OPEN_PAREN)) {
|
| - FormalParameterList parameters = _parseFormalParameterListUnchecked();
|
| - if (thisKeyword == null) {
|
| - if (holder.keyword != null) {
|
| - _reportErrorForToken(
|
| - ParserErrorCode.FUNCTION_TYPED_PARAMETER_VAR, holder.keyword);
|
| + Modifiers parseModifiers() {
|
| + Modifiers modifiers = new Modifiers();
|
| + bool progress = true;
|
| + while (progress) {
|
| + TokenType nextType = _peek().type;
|
| + if (nextType == TokenType.PERIOD ||
|
| + nextType == TokenType.LT ||
|
| + nextType == TokenType.OPEN_PAREN) {
|
| + return modifiers;
|
| + }
|
| + Keyword keyword = _currentToken.keyword;
|
| + if (keyword == Keyword.ABSTRACT) {
|
| + if (modifiers.abstractKeyword != null) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
|
| + _advance();
|
| + } else {
|
| + modifiers.abstractKeyword = getAndAdvance();
|
| }
|
| - Token question = null;
|
| - if (enableNnbd && _matches(TokenType.QUESTION)) {
|
| - question = getAndAdvance();
|
| + } else if (keyword == Keyword.CONST) {
|
| + if (modifiers.constKeyword != null) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
|
| + _advance();
|
| + } else {
|
| + modifiers.constKeyword = getAndAdvance();
|
| + }
|
| + } else if (keyword == Keyword.EXTERNAL) {
|
| + if (modifiers.externalKeyword != null) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
|
| + _advance();
|
| + } else {
|
| + modifiers.externalKeyword = getAndAdvance();
|
| + }
|
| + } else if (keyword == Keyword.FACTORY) {
|
| + if (modifiers.factoryKeyword != null) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
|
| + _advance();
|
| + } else {
|
| + modifiers.factoryKeyword = getAndAdvance();
|
| + }
|
| + } else if (keyword == Keyword.FINAL) {
|
| + if (modifiers.finalKeyword != null) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
|
| + _advance();
|
| + } else {
|
| + modifiers.finalKeyword = getAndAdvance();
|
| + }
|
| + } else if (keyword == Keyword.STATIC) {
|
| + if (modifiers.staticKeyword != null) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
|
| + _advance();
|
| + } else {
|
| + modifiers.staticKeyword = getAndAdvance();
|
| + }
|
| + } else if (keyword == Keyword.VAR) {
|
| + if (modifiers.varKeyword != null) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
|
| + _advance();
|
| + } else {
|
| + modifiers.varKeyword = getAndAdvance();
|
| }
|
| - return new FunctionTypedFormalParameter(
|
| - commentAndMetadata.comment,
|
| - commentAndMetadata.metadata,
|
| - holder.type,
|
| - new SimpleIdentifier(identifier.token, isDeclaration: true),
|
| - typeParameters,
|
| - parameters,
|
| - question: question);
|
| } else {
|
| - return new FieldFormalParameter(
|
| - commentAndMetadata.comment,
|
| - commentAndMetadata.metadata,
|
| - holder.keyword,
|
| - holder.type,
|
| - thisKeyword,
|
| - period,
|
| - identifier,
|
| - typeParameters,
|
| - parameters);
|
| - }
|
| - } else if (typeParameters != null) {
|
| - // TODO(brianwilkerson) Report an error. It looks like a function-typed
|
| - // parameter with no parameter list.
|
| - //_reportErrorForToken(ParserErrorCode.MISSING_PARAMETERS, typeParameters.endToken);
|
| - }
|
| - TypeName type = holder.type;
|
| - if (type != null) {
|
| - if (_tokenMatchesKeyword(type.name.beginToken, Keyword.VOID)) {
|
| - _reportErrorForToken(
|
| - ParserErrorCode.VOID_PARAMETER, type.name.beginToken);
|
| - } else if (holder.keyword != null &&
|
| - _tokenMatchesKeyword(holder.keyword, Keyword.VAR)) {
|
| - _reportErrorForToken(ParserErrorCode.VAR_AND_TYPE, holder.keyword);
|
| + progress = false;
|
| }
|
| }
|
| - if (thisKeyword != null) {
|
| - // TODO(brianwilkerson) If there are type parameters but no parameters,
|
| - // should we create a synthetic empty parameter list here so we can
|
| - // capture the type parameters?
|
| - return new FieldFormalParameter(
|
| - commentAndMetadata.comment,
|
| - commentAndMetadata.metadata,
|
| - holder.keyword,
|
| - holder.type,
|
| - thisKeyword,
|
| - period,
|
| - identifier,
|
| - null,
|
| - null);
|
| - }
|
| - return new SimpleFormalParameter(
|
| - commentAndMetadata.comment,
|
| - commentAndMetadata.metadata,
|
| - holder.keyword,
|
| - holder.type,
|
| - new SimpleIdentifier(identifier.token, isDeclaration: true));
|
| + return modifiers;
|
| }
|
|
|
| /**
|
| - * Parse an operator declaration. The [commentAndMetadata] is the
|
| - * documentation comment and metadata to be associated with the declaration.
|
| - * The [externalKeyword] is the 'external' token. The [returnType] is the
|
| - * return type that has already been parsed, or `null` if there was no return
|
| - * type. Return the operator declaration that was parsed.
|
| - *
|
| - * operatorDeclaration ::=
|
| - * operatorSignature (';' | functionBody)
|
| + * Parse a multiplicative expression. Return the multiplicative expression
|
| + * that was parsed.
|
| *
|
| - * operatorSignature ::=
|
| - * 'external'? returnType? 'operator' operator formalParameterList
|
| + * multiplicativeExpression ::=
|
| + * unaryExpression (multiplicativeOperator unaryExpression)*
|
| + * | 'super' (multiplicativeOperator unaryExpression)+
|
| */
|
| - MethodDeclaration parseOperator(CommentAndMetadata commentAndMetadata,
|
| - Token externalKeyword, TypeName returnType) {
|
| - Token operatorKeyword;
|
| - if (_matchesKeyword(Keyword.OPERATOR)) {
|
| - operatorKeyword = getAndAdvance();
|
| + Expression parseMultiplicativeExpression() {
|
| + Expression expression;
|
| + if (_currentToken.keyword == Keyword.SUPER &&
|
| + _currentToken.next.type.isMultiplicativeOperator) {
|
| + expression = new SuperExpression(getAndAdvance());
|
| } else {
|
| - _reportErrorForToken(
|
| - ParserErrorCode.MISSING_KEYWORD_OPERATOR, _currentToken);
|
| - operatorKeyword = _createSyntheticKeyword(Keyword.OPERATOR);
|
| + expression = parseUnaryExpression();
|
| }
|
| - return _parseOperatorAfterKeyword(
|
| - commentAndMetadata, externalKeyword, returnType, operatorKeyword);
|
| + while (_currentToken.type.isMultiplicativeOperator) {
|
| + expression = new BinaryExpression(
|
| + expression, getAndAdvance(), parseUnaryExpression());
|
| + }
|
| + return expression;
|
| }
|
|
|
| /**
|
| - * Parse a prefixed identifier. Return the prefixed identifier that was
|
| - * parsed.
|
| + * Parse a new expression. Return the new expression that was parsed.
|
| *
|
| - * prefixedIdentifier ::=
|
| - * identifier ('.' identifier)?
|
| + * This method assumes that the current token matches `Keyword.NEW`.
|
| + *
|
| + * newExpression ::=
|
| + * instanceCreationExpression
|
| */
|
| - Identifier parsePrefixedIdentifier() {
|
| - return _parsePrefixedIdentifierAfterIdentifier(parseSimpleIdentifier());
|
| - }
|
| + InstanceCreationExpression parseNewExpression() =>
|
| + parseInstanceCreationExpression(getAndAdvance());
|
|
|
| /**
|
| - * Parse a primary expression. Return the primary expression that was parsed.
|
| - *
|
| - * primary ::=
|
| - * thisExpression
|
| - * | 'super' unconditionalAssignableSelector
|
| - * | functionExpression
|
| - * | literal
|
| - * | identifier
|
| - * | newExpression
|
| - * | constObjectExpression
|
| - * | '(' expression ')'
|
| - * | argumentDefinitionTest
|
| + * Parse a non-labeled statement. Return the non-labeled statement that was
|
| + * parsed.
|
| *
|
| - * literal ::=
|
| - * nullLiteral
|
| - * | booleanLiteral
|
| - * | numericLiteral
|
| - * | stringLiteral
|
| - * | symbolLiteral
|
| - * | mapLiteral
|
| - * | listLiteral
|
| + * nonLabeledStatement ::=
|
| + * block
|
| + * | assertStatement
|
| + * | breakStatement
|
| + * | continueStatement
|
| + * | doStatement
|
| + * | forStatement
|
| + * | ifStatement
|
| + * | returnStatement
|
| + * | switchStatement
|
| + * | tryStatement
|
| + * | whileStatement
|
| + * | variableDeclarationList ';'
|
| + * | expressionStatement
|
| + * | functionSignature functionBody
|
| */
|
| - Expression parsePrimaryExpression() {
|
| - if (_matchesIdentifier()) {
|
| - // TODO(brianwilkerson) The code below was an attempt to recover from an
|
| - // error case, but it needs to be applied as a recovery only after we
|
| - // know that parsing it as an identifier doesn't work. Leaving the code as
|
| - // a reminder of how to recover.
|
| -// if (isFunctionExpression(_peek())) {
|
| -// //
|
| -// // Function expressions were allowed to have names at one point, but this is now illegal.
|
| -// //
|
| -// reportError(ParserErrorCode.NAMED_FUNCTION_EXPRESSION, getAndAdvance());
|
| -// return parseFunctionExpression();
|
| -// }
|
| - return _parsePrefixedIdentifierUnchecked();
|
| - }
|
| + Statement parseNonLabeledStatement() {
|
| + // TODO(brianwilkerson) Pass the comment and metadata on where appropriate.
|
| + CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
|
| TokenType type = _currentToken.type;
|
| - if (type == TokenType.STRING) {
|
| - return parseStringLiteral();
|
| - } else if (type == TokenType.INT) {
|
| - Token token = getAndAdvance();
|
| - int value = null;
|
| - try {
|
| - value = int.parse(token.lexeme);
|
| - } on FormatException {
|
| - // The invalid format should have been reported by the scanner.
|
| - }
|
| - return new IntegerLiteral(token, value);
|
| - }
|
| - Keyword keyword = _currentToken.keyword;
|
| - if (keyword == Keyword.NULL) {
|
| - return new NullLiteral(getAndAdvance());
|
| - } else if (keyword == Keyword.NEW) {
|
| - return _parseNewExpression();
|
| - } else if (keyword == Keyword.THIS) {
|
| - return new ThisExpression(getAndAdvance());
|
| - } else if (keyword == Keyword.SUPER) {
|
| - // TODO(paulberry): verify with Gilad that "super" must be followed by
|
| - // unconditionalAssignableSelector in this case.
|
| - return _parseAssignableSelector(
|
| - new SuperExpression(getAndAdvance()), false,
|
| - allowConditional: false);
|
| - } else if (keyword == Keyword.FALSE) {
|
| - return new BooleanLiteral(getAndAdvance(), false);
|
| - } else if (keyword == Keyword.TRUE) {
|
| - return new BooleanLiteral(getAndAdvance(), true);
|
| - }
|
| - if (type == TokenType.DOUBLE) {
|
| - Token token = getAndAdvance();
|
| - double value = 0.0;
|
| - try {
|
| - value = double.parse(token.lexeme);
|
| - } on FormatException {
|
| - // The invalid format should have been reported by the scanner.
|
| + if (type == TokenType.OPEN_CURLY_BRACKET) {
|
| + if (_tokenMatches(_peek(), TokenType.STRING)) {
|
| + Token afterString = skipStringLiteral(_currentToken.next);
|
| + if (afterString != null && afterString.type == TokenType.COLON) {
|
| + return new ExpressionStatement(
|
| + parseExpression2(), _expect(TokenType.SEMICOLON));
|
| + }
|
| }
|
| - return new DoubleLiteral(token, value);
|
| - } else if (type == TokenType.HEXADECIMAL) {
|
| - Token token = getAndAdvance();
|
| - int value = null;
|
| - try {
|
| - value = int.parse(token.lexeme.substring(2), radix: 16);
|
| - } on FormatException {
|
| - // The invalid format should have been reported by the scanner.
|
| + return parseBlock();
|
| + } else if (type == TokenType.KEYWORD &&
|
| + !_currentToken.keyword.isPseudoKeyword) {
|
| + Keyword keyword = _currentToken.keyword;
|
| + // TODO(jwren) compute some metrics to figure out a better order for this
|
| + // if-then sequence to optimize performance
|
| + if (keyword == Keyword.ASSERT) {
|
| + return parseAssertStatement();
|
| + } else if (keyword == Keyword.BREAK) {
|
| + return parseBreakStatement();
|
| + } else if (keyword == Keyword.CONTINUE) {
|
| + return parseContinueStatement();
|
| + } else if (keyword == Keyword.DO) {
|
| + return parseDoStatement();
|
| + } else if (keyword == Keyword.FOR) {
|
| + return parseForStatement();
|
| + } else if (keyword == Keyword.IF) {
|
| + return parseIfStatement();
|
| + } else if (keyword == Keyword.RETHROW) {
|
| + return new ExpressionStatement(
|
| + parseRethrowExpression(), _expect(TokenType.SEMICOLON));
|
| + } else if (keyword == Keyword.RETURN) {
|
| + return parseReturnStatement();
|
| + } else if (keyword == Keyword.SWITCH) {
|
| + return parseSwitchStatement();
|
| + } else if (keyword == Keyword.THROW) {
|
| + return new ExpressionStatement(
|
| + parseThrowExpression(), _expect(TokenType.SEMICOLON));
|
| + } else if (keyword == Keyword.TRY) {
|
| + return parseTryStatement();
|
| + } else if (keyword == Keyword.WHILE) {
|
| + return parseWhileStatement();
|
| + } else if (keyword == Keyword.VAR || keyword == Keyword.FINAL) {
|
| + return parseVariableDeclarationStatementAfterMetadata(
|
| + commentAndMetadata);
|
| + } else if (keyword == Keyword.VOID) {
|
| + TypeName returnType =
|
| + new TypeName(new SimpleIdentifier(getAndAdvance()), null);
|
| + Token next = _currentToken.next;
|
| + if (_matchesIdentifier() &&
|
| + next.matchesAny(const <TokenType>[
|
| + TokenType.OPEN_PAREN,
|
| + TokenType.OPEN_CURLY_BRACKET,
|
| + TokenType.FUNCTION,
|
| + TokenType.LT
|
| + ])) {
|
| + return _parseFunctionDeclarationStatementAfterReturnType(
|
| + commentAndMetadata, returnType);
|
| + } else {
|
| + //
|
| + // We have found an error of some kind. Try to recover.
|
| + //
|
| + if (_matchesIdentifier()) {
|
| + if (next.matchesAny(const <TokenType>[
|
| + TokenType.EQ,
|
| + TokenType.COMMA,
|
| + TokenType.SEMICOLON
|
| + ])) {
|
| + //
|
| + // We appear to have a variable declaration with a type of "void".
|
| + //
|
| + _reportErrorForNode(ParserErrorCode.VOID_VARIABLE, returnType);
|
| + return parseVariableDeclarationStatementAfterMetadata(
|
| + commentAndMetadata);
|
| + }
|
| + } else if (_matches(TokenType.CLOSE_CURLY_BRACKET)) {
|
| + //
|
| + // We appear to have found an incomplete statement at the end of a
|
| + // block. Parse it as a variable declaration.
|
| + //
|
| + return _parseVariableDeclarationStatementAfterType(
|
| + commentAndMetadata, null, returnType);
|
| + }
|
| + _reportErrorForCurrentToken(ParserErrorCode.MISSING_STATEMENT);
|
| + // TODO(brianwilkerson) Recover from this error.
|
| + return new EmptyStatement(_createSyntheticToken(TokenType.SEMICOLON));
|
| + }
|
| + } else if (keyword == Keyword.CONST) {
|
| + Token next = _currentToken.next;
|
| + if (next.matchesAny(const <TokenType>[
|
| + TokenType.LT,
|
| + TokenType.OPEN_CURLY_BRACKET,
|
| + TokenType.OPEN_SQUARE_BRACKET,
|
| + TokenType.INDEX
|
| + ])) {
|
| + return new ExpressionStatement(
|
| + parseExpression2(), _expect(TokenType.SEMICOLON));
|
| + } else if (_tokenMatches(next, TokenType.IDENTIFIER)) {
|
| + Token afterType = skipTypeName(next);
|
| + if (afterType != null) {
|
| + if (_tokenMatches(afterType, TokenType.OPEN_PAREN) ||
|
| + (_tokenMatches(afterType, TokenType.PERIOD) &&
|
| + _tokenMatches(afterType.next, TokenType.IDENTIFIER) &&
|
| + _tokenMatches(afterType.next.next, TokenType.OPEN_PAREN))) {
|
| + return new ExpressionStatement(
|
| + parseExpression2(), _expect(TokenType.SEMICOLON));
|
| + }
|
| + }
|
| + }
|
| + return parseVariableDeclarationStatementAfterMetadata(
|
| + commentAndMetadata);
|
| + } else if (keyword == Keyword.NEW ||
|
| + keyword == Keyword.TRUE ||
|
| + keyword == Keyword.FALSE ||
|
| + keyword == Keyword.NULL ||
|
| + keyword == Keyword.SUPER ||
|
| + keyword == Keyword.THIS) {
|
| + return new ExpressionStatement(
|
| + parseExpression2(), _expect(TokenType.SEMICOLON));
|
| + } else {
|
| + //
|
| + // We have found an error of some kind. Try to recover.
|
| + //
|
| + _reportErrorForCurrentToken(ParserErrorCode.MISSING_STATEMENT);
|
| + return new EmptyStatement(_createSyntheticToken(TokenType.SEMICOLON));
|
| }
|
| - return new IntegerLiteral(token, value);
|
| - } else if (keyword == Keyword.CONST) {
|
| - return parseConstExpression();
|
| - } else if (type == TokenType.OPEN_PAREN) {
|
| - if (isFunctionExpression(_currentToken)) {
|
| - return parseFunctionExpression();
|
| + } else if (_inGenerator && _matchesString(_YIELD)) {
|
| + return parseYieldStatement();
|
| + } else if (_inAsync && _matchesString(_AWAIT)) {
|
| + if (_tokenMatchesKeyword(_peek(), Keyword.FOR)) {
|
| + return parseForStatement();
|
| }
|
| - Token leftParenthesis = getAndAdvance();
|
| - bool wasInInitializer = _inInitializer;
|
| - _inInitializer = false;
|
| - try {
|
| - Expression expression = parseExpression2();
|
| - Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
|
| - return new ParenthesizedExpression(
|
| - leftParenthesis, expression, rightParenthesis);
|
| - } finally {
|
| - _inInitializer = wasInInitializer;
|
| + return new ExpressionStatement(
|
| + parseExpression2(), _expect(TokenType.SEMICOLON));
|
| + } else if (_matchesString(_AWAIT) &&
|
| + _tokenMatchesKeyword(_peek(), Keyword.FOR)) {
|
| + Token awaitToken = _currentToken;
|
| + Statement statement = parseForStatement();
|
| + if (statement is! ForStatement) {
|
| + _reportErrorForToken(
|
| + CompileTimeErrorCode.ASYNC_FOR_IN_WRONG_CONTEXT, awaitToken);
|
| }
|
| - } else if (type == TokenType.LT || _injectGenericCommentTypeList()) {
|
| - return parseListOrMapLiteral(null);
|
| - } else if (type == TokenType.OPEN_CURLY_BRACKET) {
|
| - return _parseMapLiteral(null, null);
|
| - } else if (type == TokenType.OPEN_SQUARE_BRACKET ||
|
| - type == TokenType.INDEX) {
|
| - return _parseListLiteral(null, null);
|
| - } else if (type == TokenType.QUESTION &&
|
| - _tokenMatches(_peek(), TokenType.IDENTIFIER)) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.UNEXPECTED_TOKEN, [_currentToken.lexeme]);
|
| - _advance();
|
| - return parsePrimaryExpression();
|
| - } else if (keyword == Keyword.VOID) {
|
| - //
|
| - // Recover from having a return type of "void" where a return type is not
|
| - // expected.
|
| - //
|
| - // TODO(brianwilkerson) Improve this error message.
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.UNEXPECTED_TOKEN, [_currentToken.lexeme]);
|
| - _advance();
|
| - return parsePrimaryExpression();
|
| - } else if (type == TokenType.HASH) {
|
| - return parseSymbolLiteral();
|
| + return statement;
|
| + } else if (type == TokenType.SEMICOLON) {
|
| + return parseEmptyStatement();
|
| + } else if (isInitializedVariableDeclaration()) {
|
| + return parseVariableDeclarationStatementAfterMetadata(commentAndMetadata);
|
| + } else if (isFunctionDeclaration()) {
|
| + return parseFunctionDeclarationStatement();
|
| + } else if (type == TokenType.CLOSE_CURLY_BRACKET) {
|
| + _reportErrorForCurrentToken(ParserErrorCode.MISSING_STATEMENT);
|
| + return new EmptyStatement(_createSyntheticToken(TokenType.SEMICOLON));
|
| } else {
|
| - _reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
|
| - return createSyntheticIdentifier();
|
| + return new ExpressionStatement(
|
| + parseExpression2(), _expect(TokenType.SEMICOLON));
|
| }
|
| }
|
|
|
| /**
|
| - * Parse a relational expression. Return the relational expression that was
|
| - * parsed.
|
| - *
|
| - * relationalExpression ::=
|
| - * bitwiseOrExpression ('is' '!'? type | 'as' type | relationalOperator bitwiseOrExpression)?
|
| - * | 'super' relationalOperator bitwiseOrExpression
|
| - */
|
| - Expression parseRelationalExpression() {
|
| - if (_currentToken.keyword == Keyword.SUPER &&
|
| - _currentToken.next.type.isRelationalOperator) {
|
| - Expression expression = new SuperExpression(getAndAdvance());
|
| - Token operator = getAndAdvance();
|
| - return new BinaryExpression(
|
| - expression, operator, parseBitwiseOrExpression());
|
| - }
|
| - Expression expression = parseBitwiseOrExpression();
|
| - Keyword keyword = _currentToken.keyword;
|
| - if (keyword == Keyword.AS) {
|
| - Token asOperator = getAndAdvance();
|
| - return new AsExpression(expression, asOperator, parseTypeName(true));
|
| - } else if (keyword == Keyword.IS) {
|
| - Token isOperator = getAndAdvance();
|
| - Token notOperator = null;
|
| - if (_matches(TokenType.BANG)) {
|
| - notOperator = getAndAdvance();
|
| - }
|
| - return new IsExpression(
|
| - expression, isOperator, notOperator, parseTypeName(true));
|
| - } else if (_currentToken.type.isRelationalOperator) {
|
| - Token operator = getAndAdvance();
|
| - return new BinaryExpression(
|
| - expression, operator, parseBitwiseOrExpression());
|
| - }
|
| - return expression;
|
| - }
|
| -
|
| - /**
|
| - * Parse a rethrow expression. Return the rethrow expression that was parsed.
|
| - *
|
| - * This method assumes that the current token matches `Keyword.RETHROW`.
|
| - *
|
| - * rethrowExpression ::=
|
| - * 'rethrow'
|
| - */
|
| - Expression parseRethrowExpression() => new RethrowExpression(getAndAdvance());
|
| -
|
| - /**
|
| - * Parse a return statement. Return the return statement that was parsed.
|
| - *
|
| - * This method assumes that the current token matches `Keyword.RETURN`.
|
| - *
|
| - * returnStatement ::=
|
| - * 'return' expression? ';'
|
| - */
|
| - Statement parseReturnStatement() {
|
| - Token returnKeyword = getAndAdvance();
|
| - if (_matches(TokenType.SEMICOLON)) {
|
| - return new ReturnStatement(returnKeyword, null, getAndAdvance());
|
| - }
|
| - Expression expression = parseExpression2();
|
| - Token semicolon = _expect(TokenType.SEMICOLON);
|
| - return new ReturnStatement(returnKeyword, expression, semicolon);
|
| - }
|
| -
|
| - /**
|
| - * Parse a return type. Return the return type that was parsed.
|
| - *
|
| - * returnType ::=
|
| - * 'void'
|
| - * | type
|
| - */
|
| - TypeName parseReturnType() {
|
| - if (_currentToken.keyword == Keyword.VOID) {
|
| - return new TypeName(new SimpleIdentifier(getAndAdvance()), null);
|
| - } else {
|
| - return parseTypeName(false);
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Parse a shift expression. Return the shift expression that was parsed.
|
| - *
|
| - * shiftExpression ::=
|
| - * additiveExpression (shiftOperator additiveExpression)*
|
| - * | 'super' (shiftOperator additiveExpression)+
|
| - */
|
| - Expression parseShiftExpression() {
|
| - Expression expression;
|
| - if (_currentToken.keyword == Keyword.SUPER &&
|
| - _currentToken.next.type.isShiftOperator) {
|
| - expression = new SuperExpression(getAndAdvance());
|
| - } else {
|
| - expression = parseAdditiveExpression();
|
| - }
|
| - while (_currentToken.type.isShiftOperator) {
|
| - expression = new BinaryExpression(
|
| - expression, getAndAdvance(), parseAdditiveExpression());
|
| - }
|
| - return expression;
|
| - }
|
| -
|
| - /**
|
| - * Parse a simple identifier. Return the simple identifier that was parsed.
|
| - *
|
| - * identifier ::=
|
| - * IDENTIFIER
|
| - */
|
| - SimpleIdentifier parseSimpleIdentifier({bool isDeclaration: false}) {
|
| - if (_matchesIdentifier()) {
|
| - return _parseSimpleIdentifierUnchecked(isDeclaration: isDeclaration);
|
| - }
|
| - _reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
|
| - return createSyntheticIdentifier(isDeclaration: isDeclaration);
|
| - }
|
| -
|
| - /**
|
| - * Parse a statement, starting with the given [token]. Return the statement
|
| - * that was parsed, or `null` if the tokens do not represent a recognizable
|
| - * statement.
|
| - */
|
| - Statement parseStatement(Token token) {
|
| - _currentToken = token;
|
| - return parseStatement2();
|
| - }
|
| -
|
| - /**
|
| - * Parse a statement. Return the statement that was parsed.
|
| - *
|
| - * statement ::=
|
| - * label* nonLabeledStatement
|
| - */
|
| - Statement parseStatement2() {
|
| - List<Label> labels = null;
|
| - while (_matchesIdentifier() && _currentToken.next.type == TokenType.COLON) {
|
| - Label label = parseLabel(isDeclaration: true);
|
| - if (labels == null) {
|
| - labels = <Label>[label];
|
| - } else {
|
| - labels.add(label);
|
| - }
|
| - }
|
| - Statement statement = _parseNonLabeledStatement();
|
| - if (labels == null) {
|
| - return statement;
|
| - }
|
| - return new LabeledStatement(labels, statement);
|
| - }
|
| -
|
| - /**
|
| - * Parse a sequence of statements, starting with the given [token]. Return the
|
| - * statements that were parsed, or `null` if the tokens do not represent a
|
| - * recognizable sequence of statements.
|
| - */
|
| - List<Statement> parseStatements(Token token) {
|
| - _currentToken = token;
|
| - return _parseStatementList();
|
| - }
|
| -
|
| - /**
|
| - * Parse a string literal. Return the string literal that was parsed.
|
| - *
|
| - * stringLiteral ::=
|
| - * MULTI_LINE_STRING+
|
| - * | SINGLE_LINE_STRING+
|
| - */
|
| - StringLiteral parseStringLiteral() {
|
| - if (_matches(TokenType.STRING)) {
|
| - return _parseStringLiteralUnchecked();
|
| - }
|
| - _reportErrorForCurrentToken(ParserErrorCode.EXPECTED_STRING_LITERAL);
|
| - return createSyntheticStringLiteral();
|
| - }
|
| -
|
| - /**
|
| - * Parse a super constructor invocation. Return the super constructor
|
| - * invocation that was parsed.
|
| - *
|
| - * This method assumes that the current token matches [Keyword.SUPER].
|
| - *
|
| - * superConstructorInvocation ::=
|
| - * 'super' ('.' identifier)? arguments
|
| - */
|
| - SuperConstructorInvocation parseSuperConstructorInvocation() {
|
| - Token keyword = getAndAdvance();
|
| - Token period = null;
|
| - SimpleIdentifier constructorName = null;
|
| - if (_matches(TokenType.PERIOD)) {
|
| - period = getAndAdvance();
|
| - constructorName = parseSimpleIdentifier();
|
| - }
|
| - ArgumentList argumentList = _parseArgumentListChecked();
|
| - return new SuperConstructorInvocation(
|
| - keyword, period, constructorName, argumentList);
|
| - }
|
| -
|
| - /**
|
| - * Parse a switch statement. Return the switch statement that was parsed.
|
| - *
|
| - * switchStatement ::=
|
| - * 'switch' '(' expression ')' '{' switchCase* defaultCase? '}'
|
| - *
|
| - * switchCase ::=
|
| - * label* ('case' expression ':') statements
|
| - *
|
| - * defaultCase ::=
|
| - * label* 'default' ':' statements
|
| - */
|
| - SwitchStatement parseSwitchStatement() {
|
| - bool wasInSwitch = _inSwitch;
|
| - _inSwitch = true;
|
| - try {
|
| - HashSet<String> definedLabels = new HashSet<String>();
|
| - Token keyword = _expectKeyword(Keyword.SWITCH);
|
| - Token leftParenthesis = _expect(TokenType.OPEN_PAREN);
|
| - Expression expression = parseExpression2();
|
| - Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
|
| - Token leftBracket = _expect(TokenType.OPEN_CURLY_BRACKET);
|
| - Token defaultKeyword = null;
|
| - List<SwitchMember> members = <SwitchMember>[];
|
| - TokenType type = _currentToken.type;
|
| - while (type != TokenType.EOF && type != TokenType.CLOSE_CURLY_BRACKET) {
|
| - List<Label> labels = <Label>[];
|
| - while (
|
| - _matchesIdentifier() && _tokenMatches(_peek(), TokenType.COLON)) {
|
| - SimpleIdentifier identifier =
|
| - _parseSimpleIdentifierUnchecked(isDeclaration: true);
|
| - String label = identifier.token.lexeme;
|
| - if (definedLabels.contains(label)) {
|
| - _reportErrorForToken(
|
| - ParserErrorCode.DUPLICATE_LABEL_IN_SWITCH_STATEMENT,
|
| - identifier.token,
|
| - [label]);
|
| - } else {
|
| - definedLabels.add(label);
|
| - }
|
| - Token colon = getAndAdvance();
|
| - labels.add(new Label(identifier, colon));
|
| - }
|
| - Keyword keyword = _currentToken.keyword;
|
| - if (keyword == Keyword.CASE) {
|
| - Token caseKeyword = getAndAdvance();
|
| - Expression caseExpression = parseExpression2();
|
| - Token colon = _expect(TokenType.COLON);
|
| - members.add(new SwitchCase(labels, caseKeyword, caseExpression, colon,
|
| - _parseStatementList()));
|
| - if (defaultKeyword != null) {
|
| - _reportErrorForToken(
|
| - ParserErrorCode.SWITCH_HAS_CASE_AFTER_DEFAULT_CASE,
|
| - caseKeyword);
|
| - }
|
| - } else if (keyword == Keyword.DEFAULT) {
|
| - if (defaultKeyword != null) {
|
| - _reportErrorForToken(
|
| - ParserErrorCode.SWITCH_HAS_MULTIPLE_DEFAULT_CASES, _peek());
|
| - }
|
| - defaultKeyword = getAndAdvance();
|
| - Token colon = _expect(TokenType.COLON);
|
| - members.add(new SwitchDefault(
|
| - labels, defaultKeyword, colon, _parseStatementList()));
|
| - } else {
|
| - // We need to advance, otherwise we could end up in an infinite loop,
|
| - // but this could be a lot smarter about recovering from the error.
|
| - _reportErrorForCurrentToken(ParserErrorCode.EXPECTED_CASE_OR_DEFAULT);
|
| - bool atEndOrNextMember() {
|
| - TokenType type = _currentToken.type;
|
| - if (type == TokenType.EOF ||
|
| - type == TokenType.CLOSE_CURLY_BRACKET) {
|
| - return true;
|
| - }
|
| - Keyword keyword = _currentToken.keyword;
|
| - return keyword == Keyword.CASE || keyword == Keyword.DEFAULT;
|
| - }
|
| -
|
| - while (!atEndOrNextMember()) {
|
| - _advance();
|
| - }
|
| - }
|
| - type = _currentToken.type;
|
| - }
|
| - Token rightBracket = _expect(TokenType.CLOSE_CURLY_BRACKET);
|
| - return new SwitchStatement(keyword, leftParenthesis, expression,
|
| - rightParenthesis, leftBracket, members, rightBracket);
|
| - } finally {
|
| - _inSwitch = wasInSwitch;
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Parse a symbol literal. Return the symbol literal that was parsed.
|
| - *
|
| - * This method assumes that the current token matches [TokenType.HASH].
|
| - *
|
| - * symbolLiteral ::=
|
| - * '#' identifier ('.' identifier)*
|
| - */
|
| - SymbolLiteral parseSymbolLiteral() {
|
| - Token poundSign = getAndAdvance();
|
| - List<Token> components = <Token>[];
|
| - if (_matchesIdentifier()) {
|
| - components.add(getAndAdvance());
|
| - while (_optional(TokenType.PERIOD)) {
|
| - if (_matchesIdentifier()) {
|
| - components.add(getAndAdvance());
|
| - } else {
|
| - _reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
|
| - components.add(_createSyntheticToken(TokenType.IDENTIFIER));
|
| - break;
|
| - }
|
| - }
|
| - } else if (_currentToken.isOperator) {
|
| - components.add(getAndAdvance());
|
| - } else if (_matchesKeyword(Keyword.VOID)) {
|
| - components.add(getAndAdvance());
|
| - } else {
|
| - _reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
|
| - components.add(_createSyntheticToken(TokenType.IDENTIFIER));
|
| - }
|
| - return new SymbolLiteral(poundSign, components);
|
| - }
|
| -
|
| - /**
|
| - * Parse a throw expression. Return the throw expression that was parsed.
|
| - *
|
| - * This method assumes that the current token matches [Keyword.THROW].
|
| - *
|
| - * throwExpression ::=
|
| - * 'throw' expression
|
| - */
|
| - Expression parseThrowExpression() {
|
| - Token keyword = getAndAdvance();
|
| - TokenType type = _currentToken.type;
|
| - if (type == TokenType.SEMICOLON || type == TokenType.CLOSE_PAREN) {
|
| - _reportErrorForToken(
|
| - ParserErrorCode.MISSING_EXPRESSION_IN_THROW, _currentToken);
|
| - return new ThrowExpression(keyword, createSyntheticIdentifier());
|
| - }
|
| - Expression expression = parseExpression2();
|
| - return new ThrowExpression(keyword, expression);
|
| - }
|
| -
|
| - /**
|
| - * Parse a throw expression. Return the throw expression that was parsed.
|
| - *
|
| - * This method assumes that the current token matches [Keyword.THROW].
|
| - *
|
| - * throwExpressionWithoutCascade ::=
|
| - * 'throw' expressionWithoutCascade
|
| - */
|
| - Expression parseThrowExpressionWithoutCascade() {
|
| - Token keyword = getAndAdvance();
|
| - TokenType type = _currentToken.type;
|
| - if (type == TokenType.SEMICOLON || type == TokenType.CLOSE_PAREN) {
|
| - _reportErrorForToken(
|
| - ParserErrorCode.MISSING_EXPRESSION_IN_THROW, _currentToken);
|
| - return new ThrowExpression(keyword, createSyntheticIdentifier());
|
| - }
|
| - Expression expression = parseExpressionWithoutCascade();
|
| - return new ThrowExpression(keyword, expression);
|
| - }
|
| -
|
| - /**
|
| - * Parse a try statement. Return the try statement that was parsed.
|
| - *
|
| - * This method assumes that the current token matches [Keyword.TRY].
|
| - *
|
| - * tryStatement ::=
|
| - * 'try' block (onPart+ finallyPart? | finallyPart)
|
| - *
|
| - * onPart ::=
|
| - * catchPart block
|
| - * | 'on' type catchPart? block
|
| - *
|
| - * catchPart ::=
|
| - * 'catch' '(' identifier (',' identifier)? ')'
|
| - *
|
| - * finallyPart ::=
|
| - * 'finally' block
|
| - */
|
| - Statement parseTryStatement() {
|
| - Token tryKeyword = getAndAdvance();
|
| - Block body = _parseBlockChecked();
|
| - List<CatchClause> catchClauses = <CatchClause>[];
|
| - Block finallyClause = null;
|
| - while (_matchesString(_ON) || _matchesKeyword(Keyword.CATCH)) {
|
| - Token onKeyword = null;
|
| - TypeName exceptionType = null;
|
| - if (_matchesString(_ON)) {
|
| - onKeyword = getAndAdvance();
|
| - exceptionType = parseTypeName(false);
|
| - }
|
| - Token catchKeyword = null;
|
| - Token leftParenthesis = null;
|
| - SimpleIdentifier exceptionParameter = null;
|
| - Token comma = null;
|
| - SimpleIdentifier stackTraceParameter = null;
|
| - Token rightParenthesis = null;
|
| - if (_matchesKeyword(Keyword.CATCH)) {
|
| - catchKeyword = getAndAdvance();
|
| - leftParenthesis = _expect(TokenType.OPEN_PAREN);
|
| - exceptionParameter = parseSimpleIdentifier(isDeclaration: true);
|
| - if (_matches(TokenType.COMMA)) {
|
| - comma = getAndAdvance();
|
| - stackTraceParameter = parseSimpleIdentifier(isDeclaration: true);
|
| - }
|
| - rightParenthesis = _expect(TokenType.CLOSE_PAREN);
|
| - }
|
| - Block catchBody = _parseBlockChecked();
|
| - catchClauses.add(new CatchClause(
|
| - onKeyword,
|
| - exceptionType,
|
| - catchKeyword,
|
| - leftParenthesis,
|
| - exceptionParameter,
|
| - comma,
|
| - stackTraceParameter,
|
| - rightParenthesis,
|
| - catchBody));
|
| - }
|
| - Token finallyKeyword = null;
|
| - if (_matchesKeyword(Keyword.FINALLY)) {
|
| - finallyKeyword = getAndAdvance();
|
| - finallyClause = _parseBlockChecked();
|
| - } else if (catchClauses.isEmpty) {
|
| - _reportErrorForCurrentToken(ParserErrorCode.MISSING_CATCH_OR_FINALLY);
|
| - }
|
| - return new TryStatement(
|
| - tryKeyword, body, catchClauses, finallyKeyword, finallyClause);
|
| - }
|
| -
|
| - /**
|
| - * Parse a list of type arguments. Return the type argument list that was
|
| - * parsed.
|
| - *
|
| - * This method assumes that the current token matches `TokenType.LT`.
|
| - *
|
| - * typeArguments ::=
|
| - * '<' typeList '>'
|
| - *
|
| - * typeList ::=
|
| - * type (',' type)*
|
| - */
|
| - TypeArgumentList parseTypeArgumentList() {
|
| - Token leftBracket = getAndAdvance();
|
| - List<TypeName> arguments = <TypeName>[parseTypeName(false)];
|
| - while (_optional(TokenType.COMMA)) {
|
| - arguments.add(parseTypeName(false));
|
| - }
|
| - Token rightBracket = _expectGt();
|
| - return new TypeArgumentList(leftBracket, arguments, rightBracket);
|
| - }
|
| -
|
| - /**
|
| - * Parse a type name. Return the type name that was parsed.
|
| - *
|
| - * type ::=
|
| - * qualified typeArguments?
|
| - */
|
| - TypeName parseTypeName(bool inExpression) {
|
| - TypeName realType = _parseTypeName(inExpression);
|
| - // If this is followed by a generic method type comment, allow the comment
|
| - // type to replace the real type name.
|
| - // TODO(jmesserly): this feels like a big hammer. Can we restrict it to
|
| - // only work inside generic methods?
|
| - TypeName typeFromComment = _parseOptionalTypeNameComment();
|
| - return typeFromComment ?? realType;
|
| - }
|
| -
|
| - /**
|
| - * Parse a type parameter. Return the type parameter that was parsed.
|
| - *
|
| - * typeParameter ::=
|
| - * metadata name ('extends' bound)?
|
| - */
|
| - TypeParameter parseTypeParameter() {
|
| - CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
|
| - SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
|
| - if (_matchesKeyword(Keyword.EXTENDS)) {
|
| - Token keyword = getAndAdvance();
|
| - TypeName bound = parseTypeName(false);
|
| - return new TypeParameter(commentAndMetadata.comment,
|
| - commentAndMetadata.metadata, name, keyword, bound);
|
| - }
|
| - return new TypeParameter(commentAndMetadata.comment,
|
| - commentAndMetadata.metadata, name, null, null);
|
| - }
|
| -
|
| - /**
|
| - * Parse a list of type parameters. Return the list of type parameters that
|
| - * were parsed.
|
| - *
|
| - * This method assumes that the current token matches `TokenType.LT`.
|
| - *
|
| - * typeParameterList ::=
|
| - * '<' typeParameter (',' typeParameter)* '>'
|
| - */
|
| - TypeParameterList parseTypeParameterList() {
|
| - Token leftBracket = getAndAdvance();
|
| - List<TypeParameter> typeParameters = <TypeParameter>[parseTypeParameter()];
|
| - while (_optional(TokenType.COMMA)) {
|
| - typeParameters.add(parseTypeParameter());
|
| - }
|
| - Token rightBracket = _expectGt();
|
| - return new TypeParameterList(leftBracket, typeParameters, rightBracket);
|
| - }
|
| -
|
| - /**
|
| - * Parse a unary expression. Return the unary expression that was parsed.
|
| - *
|
| - * unaryExpression ::=
|
| - * prefixOperator unaryExpression
|
| - * | awaitExpression
|
| - * | postfixExpression
|
| - * | unaryOperator 'super'
|
| - * | '-' 'super'
|
| - * | incrementOperator assignableExpression
|
| - */
|
| - Expression parseUnaryExpression() {
|
| - TokenType type = _currentToken.type;
|
| - if (type == TokenType.MINUS ||
|
| - type == TokenType.BANG ||
|
| - type == TokenType.TILDE) {
|
| - Token operator = getAndAdvance();
|
| - if (_matchesKeyword(Keyword.SUPER)) {
|
| - TokenType nextType = _peek().type;
|
| - if (nextType == TokenType.OPEN_SQUARE_BRACKET ||
|
| - nextType == TokenType.PERIOD) {
|
| - // "prefixOperator unaryExpression"
|
| - // --> "prefixOperator postfixExpression"
|
| - // --> "prefixOperator primary selector*"
|
| - // --> "prefixOperator 'super' assignableSelector selector*"
|
| - return new PrefixExpression(operator, parseUnaryExpression());
|
| - }
|
| - return new PrefixExpression(
|
| - operator, new SuperExpression(getAndAdvance()));
|
| - }
|
| - return new PrefixExpression(operator, parseUnaryExpression());
|
| - } else if (_currentToken.type.isIncrementOperator) {
|
| - Token operator = getAndAdvance();
|
| - if (_matchesKeyword(Keyword.SUPER)) {
|
| - TokenType nextType = _peek().type;
|
| - if (nextType == TokenType.OPEN_SQUARE_BRACKET ||
|
| - nextType == TokenType.PERIOD) {
|
| - // --> "prefixOperator 'super' assignableSelector selector*"
|
| - return new PrefixExpression(operator, parseUnaryExpression());
|
| - }
|
| - //
|
| - // Even though it is not valid to use an incrementing operator
|
| - // ('++' or '--') before 'super', we can (and therefore must) interpret
|
| - // "--super" as semantically equivalent to "-(-super)". Unfortunately,
|
| - // we cannot do the same for "++super" because "+super" is also not
|
| - // valid.
|
| - //
|
| - if (type == TokenType.MINUS_MINUS) {
|
| - Token firstOperator = _createToken(operator, TokenType.MINUS);
|
| - Token secondOperator =
|
| - new Token(TokenType.MINUS, operator.offset + 1);
|
| - secondOperator.setNext(_currentToken);
|
| - firstOperator.setNext(secondOperator);
|
| - operator.previous.setNext(firstOperator);
|
| - return new PrefixExpression(
|
| - firstOperator,
|
| - new PrefixExpression(
|
| - secondOperator, new SuperExpression(getAndAdvance())));
|
| - }
|
| - // Invalid operator before 'super'
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.INVALID_OPERATOR_FOR_SUPER, [operator.lexeme]);
|
| - return new PrefixExpression(
|
| - operator, new SuperExpression(getAndAdvance()));
|
| - }
|
| - return new PrefixExpression(
|
| - operator, _parseAssignableExpressionNotStartingWithSuper(false));
|
| - } else if (type == TokenType.PLUS) {
|
| - _reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
|
| - return createSyntheticIdentifier();
|
| - } else if (_inAsync && _matchesString(_AWAIT)) {
|
| - return parseAwaitExpression();
|
| - }
|
| - return _parsePostfixExpression();
|
| - }
|
| -
|
| - /**
|
| - * Parse a variable declaration. Return the variable declaration that was
|
| - * parsed.
|
| - *
|
| - * variableDeclaration ::=
|
| - * identifier ('=' expression)?
|
| - */
|
| - VariableDeclaration parseVariableDeclaration() {
|
| - // TODO(paulberry): prior to the fix for bug 23204, we permitted
|
| - // annotations before variable declarations (e.g. "String @deprecated s;").
|
| - // Although such constructions are prohibited by the spec, we may want to
|
| - // consider handling them anyway to allow for better parser recovery in the
|
| - // event that the user erroneously tries to use them. However, as a
|
| - // counterargument, this would likely degrade parser recovery in the event
|
| - // of a construct like "class C { int @deprecated foo() {} }" (i.e. the
|
| - // user is in the middle of inserting "int bar;" prior to
|
| - // "@deprecated foo() {}").
|
| - SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
|
| - Token equals = null;
|
| - Expression initializer = null;
|
| - if (_matches(TokenType.EQ)) {
|
| - equals = getAndAdvance();
|
| - initializer = parseExpression2();
|
| - }
|
| - return new VariableDeclaration(name, equals, initializer);
|
| - }
|
| -
|
| - /**
|
| - * Parse a variable declaration list. The [commentAndMetadata] is the metadata
|
| - * to be associated with the variable declaration list. Return the variable
|
| - * declaration list that was parsed.
|
| - *
|
| - * variableDeclarationList ::=
|
| - * finalConstVarOrType variableDeclaration (',' variableDeclaration)*
|
| - */
|
| - VariableDeclarationList parseVariableDeclarationListAfterMetadata(
|
| - CommentAndMetadata commentAndMetadata) {
|
| - FinalConstVarOrType holder = parseFinalConstVarOrType(false);
|
| - return parseVariableDeclarationListAfterType(
|
| - commentAndMetadata, holder.keyword, holder.type);
|
| - }
|
| -
|
| - /**
|
| - * Parse a variable declaration list. The [commentAndMetadata] is the metadata
|
| - * to be associated with the variable declaration list, or `null` if there is
|
| - * no attempt at parsing the comment and metadata. The [keyword] is the token
|
| - * representing the 'final', 'const' or 'var' keyword, or `null` if there is
|
| - * no keyword. The [type] is the type of the variables in the list. Return the
|
| - * variable declaration list that was parsed.
|
| - *
|
| - * variableDeclarationList ::=
|
| - * finalConstVarOrType variableDeclaration (',' variableDeclaration)*
|
| - */
|
| - VariableDeclarationList parseVariableDeclarationListAfterType(
|
| - CommentAndMetadata commentAndMetadata, Token keyword, TypeName type) {
|
| - if (type != null &&
|
| - keyword != null &&
|
| - _tokenMatchesKeyword(keyword, Keyword.VAR)) {
|
| - _reportErrorForToken(ParserErrorCode.VAR_AND_TYPE, keyword);
|
| - }
|
| - List<VariableDeclaration> variables = <VariableDeclaration>[
|
| - parseVariableDeclaration()
|
| - ];
|
| - while (_optional(TokenType.COMMA)) {
|
| - variables.add(parseVariableDeclaration());
|
| - }
|
| - return new VariableDeclarationList(commentAndMetadata?.comment,
|
| - commentAndMetadata?.metadata, keyword, type, variables);
|
| - }
|
| -
|
| - /**
|
| - * Parse a variable declaration statement. The [commentAndMetadata] is the
|
| - * metadata to be associated with the variable declaration statement, or
|
| - * `null` if there is no attempt at parsing the comment and metadata. Return
|
| - * the variable declaration statement that was parsed.
|
| - *
|
| - * variableDeclarationStatement ::=
|
| - * variableDeclarationList ';'
|
| - */
|
| - VariableDeclarationStatement parseVariableDeclarationStatementAfterMetadata(
|
| - CommentAndMetadata commentAndMetadata) {
|
| - // Token startToken = currentToken;
|
| - VariableDeclarationList variableList =
|
| - parseVariableDeclarationListAfterMetadata(commentAndMetadata);
|
| -// if (!matches(TokenType.SEMICOLON)) {
|
| -// if (matches(startToken, Keyword.VAR) && isTypedIdentifier(startToken.getNext())) {
|
| -// // TODO(brianwilkerson) This appears to be of the form "var type variable". We should do
|
| -// // a better job of recovering in this case.
|
| -// }
|
| -// }
|
| - Token semicolon = _expect(TokenType.SEMICOLON);
|
| - return new VariableDeclarationStatement(variableList, semicolon);
|
| - }
|
| -
|
| - /**
|
| - * Parse a while statement. Return the while statement that was parsed.
|
| - *
|
| - * This method assumes that the current token matches [Keyword.WHILE].
|
| - *
|
| - * whileStatement ::=
|
| - * 'while' '(' expression ')' statement
|
| - */
|
| - Statement parseWhileStatement() {
|
| - bool wasInLoop = _inLoop;
|
| - _inLoop = true;
|
| - try {
|
| - Token keyword = getAndAdvance();
|
| - Token leftParenthesis = _expect(TokenType.OPEN_PAREN);
|
| - Expression condition = parseExpression2();
|
| - Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
|
| - Statement body = parseStatement2();
|
| - return new WhileStatement(
|
| - keyword, leftParenthesis, condition, rightParenthesis, body);
|
| - } finally {
|
| - _inLoop = wasInLoop;
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Parse a with clause. Return the with clause that was parsed.
|
| - *
|
| - * This method assumes that the current token matches `Keyword.WITH`.
|
| - *
|
| - * withClause ::=
|
| - * 'with' typeName (',' typeName)*
|
| - */
|
| - WithClause parseWithClause() {
|
| - Token withKeyword = getAndAdvance();
|
| - List<TypeName> types = <TypeName>[parseTypeName(false)];
|
| - while (_optional(TokenType.COMMA)) {
|
| - types.add(parseTypeName(false));
|
| - }
|
| - return new WithClause(withKeyword, types);
|
| - }
|
| -
|
| - /**
|
| - * Parse a yield statement. Return the yield statement that was parsed.
|
| - *
|
| - * This method assumes that the current token matches [Keyword.YIELD].
|
| - *
|
| - * yieldStatement ::=
|
| - * 'yield' '*'? expression ';'
|
| - */
|
| - YieldStatement parseYieldStatement() {
|
| - Token yieldToken = getAndAdvance();
|
| - Token star = null;
|
| - if (_matches(TokenType.STAR)) {
|
| - star = getAndAdvance();
|
| - }
|
| - Expression expression = parseExpression2();
|
| - Token semicolon = _expect(TokenType.SEMICOLON);
|
| - return new YieldStatement(yieldToken, star, expression, semicolon);
|
| - }
|
| -
|
| - /**
|
| - * Parse a prefixed identifier, starting at the [startToken], without actually
|
| - * creating a prefixed identifier or changing the current token. Return the
|
| - * token following the prefixed identifier that was parsed, or `null` if the
|
| - * given token is not the first token in a valid prefixed identifier.
|
| + * Parse a normal formal parameter. Return the normal formal parameter that
|
| + * was parsed.
|
| *
|
| - * This method must be kept in sync with [parsePrefixedIdentifier].
|
| + * normalFormalParameter ::=
|
| + * functionSignature
|
| + * | fieldFormalParameter
|
| + * | simpleFormalParameter
|
| *
|
| - * prefixedIdentifier ::=
|
| - * identifier ('.' identifier)?
|
| + * functionSignature:
|
| + * metadata returnType? identifier typeParameters? formalParameterList
|
| + *
|
| + * fieldFormalParameter ::=
|
| + * metadata finalConstVarOrType? 'this' '.' identifier
|
| + *
|
| + * simpleFormalParameter ::=
|
| + * declaredIdentifier
|
| + * | metadata identifier
|
| */
|
| - Token skipPrefixedIdentifier(Token startToken) {
|
| - Token token = skipSimpleIdentifier(startToken);
|
| - if (token == null) {
|
| - return null;
|
| - } else if (!_tokenMatches(token, TokenType.PERIOD)) {
|
| - return token;
|
| + NormalFormalParameter parseNormalFormalParameter() {
|
| + CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
|
| + FinalConstVarOrType holder = parseFinalConstVarOrType(true);
|
| + Token thisKeyword = null;
|
| + Token period = null;
|
| + if (_matchesKeyword(Keyword.THIS)) {
|
| + thisKeyword = getAndAdvance();
|
| + period = _expect(TokenType.PERIOD);
|
| }
|
| - token = token.next;
|
| - Token nextToken = skipSimpleIdentifier(token);
|
| - if (nextToken != null) {
|
| - return nextToken;
|
| - } else if (_tokenMatches(token, TokenType.CLOSE_PAREN) ||
|
| - _tokenMatches(token, TokenType.COMMA)) {
|
| - // If the `id.` is followed by something that cannot produce a valid
|
| - // structure then assume this is a prefixed identifier but missing the
|
| - // trailing identifier
|
| - return token;
|
| + SimpleIdentifier identifier = parseSimpleIdentifier();
|
| + TypeParameterList typeParameters = _parseGenericMethodTypeParameters();
|
| + if (_matches(TokenType.OPEN_PAREN)) {
|
| + FormalParameterList parameters = _parseFormalParameterListUnchecked();
|
| + if (thisKeyword == null) {
|
| + if (holder.keyword != null) {
|
| + _reportErrorForToken(
|
| + ParserErrorCode.FUNCTION_TYPED_PARAMETER_VAR, holder.keyword);
|
| + }
|
| + Token question = null;
|
| + if (enableNnbd && _matches(TokenType.QUESTION)) {
|
| + question = getAndAdvance();
|
| + }
|
| + return new FunctionTypedFormalParameter(
|
| + commentAndMetadata.comment,
|
| + commentAndMetadata.metadata,
|
| + holder.type,
|
| + new SimpleIdentifier(identifier.token, isDeclaration: true),
|
| + typeParameters,
|
| + parameters,
|
| + question: question);
|
| + } else {
|
| + return new FieldFormalParameter(
|
| + commentAndMetadata.comment,
|
| + commentAndMetadata.metadata,
|
| + holder.keyword,
|
| + holder.type,
|
| + thisKeyword,
|
| + period,
|
| + identifier,
|
| + typeParameters,
|
| + parameters);
|
| + }
|
| + } else if (typeParameters != null) {
|
| + // TODO(brianwilkerson) Report an error. It looks like a function-typed
|
| + // parameter with no parameter list.
|
| + //_reportErrorForToken(ParserErrorCode.MISSING_PARAMETERS, typeParameters.endToken);
|
| }
|
| - return null;
|
| + TypeName type = holder.type;
|
| + if (type != null) {
|
| + if (_tokenMatchesKeyword(type.name.beginToken, Keyword.VOID)) {
|
| + _reportErrorForToken(
|
| + ParserErrorCode.VOID_PARAMETER, type.name.beginToken);
|
| + } else if (holder.keyword != null &&
|
| + _tokenMatchesKeyword(holder.keyword, Keyword.VAR)) {
|
| + _reportErrorForToken(ParserErrorCode.VAR_AND_TYPE, holder.keyword);
|
| + }
|
| + }
|
| + if (thisKeyword != null) {
|
| + // TODO(brianwilkerson) If there are type parameters but no parameters,
|
| + // should we create a synthetic empty parameter list here so we can
|
| + // capture the type parameters?
|
| + return new FieldFormalParameter(
|
| + commentAndMetadata.comment,
|
| + commentAndMetadata.metadata,
|
| + holder.keyword,
|
| + holder.type,
|
| + thisKeyword,
|
| + period,
|
| + identifier,
|
| + null,
|
| + null);
|
| + }
|
| + return new SimpleFormalParameter(
|
| + commentAndMetadata.comment,
|
| + commentAndMetadata.metadata,
|
| + holder.keyword,
|
| + holder.type,
|
| + new SimpleIdentifier(identifier.token, isDeclaration: true));
|
| }
|
|
|
| /**
|
| - * Parse a return type, starting at the [startToken], without actually
|
| - * creating a return type or changing the current token. Return the token
|
| - * following the return type that was parsed, or `null` if the given token is
|
| - * not the first token in a valid return type.
|
| + * Parse an operator declaration. The [commentAndMetadata] is the
|
| + * documentation comment and metadata to be associated with the declaration.
|
| + * The [externalKeyword] is the 'external' token. The [returnType] is the
|
| + * return type that has already been parsed, or `null` if there was no return
|
| + * type. Return the operator declaration that was parsed.
|
| *
|
| - * This method must be kept in sync with [parseReturnType].
|
| + * operatorDeclaration ::=
|
| + * operatorSignature (';' | functionBody)
|
| *
|
| - * returnType ::=
|
| - * 'void'
|
| - * | type
|
| + * operatorSignature ::=
|
| + * 'external'? returnType? 'operator' operator formalParameterList
|
| */
|
| - Token skipReturnType(Token startToken) {
|
| - if (_tokenMatchesKeyword(startToken, Keyword.VOID)) {
|
| - return startToken.next;
|
| + MethodDeclaration parseOperator(CommentAndMetadata commentAndMetadata,
|
| + Token externalKeyword, TypeName returnType) {
|
| + Token operatorKeyword;
|
| + if (_matchesKeyword(Keyword.OPERATOR)) {
|
| + operatorKeyword = getAndAdvance();
|
| } else {
|
| - return skipTypeName(startToken);
|
| + _reportErrorForToken(
|
| + ParserErrorCode.MISSING_KEYWORD_OPERATOR, _currentToken);
|
| + operatorKeyword = _createSyntheticKeyword(Keyword.OPERATOR);
|
| }
|
| + return _parseOperatorAfterKeyword(
|
| + commentAndMetadata, externalKeyword, returnType, operatorKeyword);
|
| }
|
|
|
| /**
|
| - * Parse a simple identifier, starting at the [startToken], without actually
|
| - * creating a simple identifier or changing the current token. Return the
|
| - * token following the simple identifier that was parsed, or `null` if the
|
| - * given token is not the first token in a valid simple identifier.
|
| - *
|
| - * This method must be kept in sync with [parseSimpleIdentifier].
|
| + * Parse a part or part-of directive. The [commentAndMetadata] is the metadata
|
| + * to be associated with the directive. Return the part or part-of directive
|
| + * that was parsed.
|
| *
|
| - * identifier ::=
|
| - * IDENTIFIER
|
| - */
|
| - Token skipSimpleIdentifier(Token startToken) {
|
| - if (_tokenMatches(startToken, TokenType.IDENTIFIER) ||
|
| - _tokenMatchesPseudoKeyword(startToken)) {
|
| - return startToken.next;
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - /**
|
| - * Parse a string literal, starting at the [startToken], without actually
|
| - * creating a string literal or changing the current token. Return the token
|
| - * following the string literal that was parsed, or `null` if the given token
|
| - * is not the first token in a valid string literal.
|
| + * This method assumes that the current token matches `Keyword.PART`.
|
| *
|
| - * This method must be kept in sync with [parseStringLiteral].
|
| + * partDirective ::=
|
| + * metadata 'part' stringLiteral ';'
|
| *
|
| - * stringLiteral ::=
|
| - * MULTI_LINE_STRING+
|
| - * | SINGLE_LINE_STRING+
|
| + * partOfDirective ::=
|
| + * metadata 'part' 'of' identifier ';'
|
| */
|
| - Token skipStringLiteral(Token startToken) {
|
| - Token token = startToken;
|
| - while (token != null && _tokenMatches(token, TokenType.STRING)) {
|
| - token = token.next;
|
| - TokenType type = token.type;
|
| - if (type == TokenType.STRING_INTERPOLATION_EXPRESSION ||
|
| - type == TokenType.STRING_INTERPOLATION_IDENTIFIER) {
|
| - token = _skipStringInterpolation(token);
|
| - }
|
| - }
|
| - if (identical(token, startToken)) {
|
| - return null;
|
| + Directive parsePartOrPartOfDirective(CommentAndMetadata commentAndMetadata) {
|
| + if (_tokenMatchesString(_peek(), _OF)) {
|
| + return _parsePartOfDirective(commentAndMetadata);
|
| }
|
| - return token;
|
| + return _parsePartDirective(commentAndMetadata);
|
| }
|
|
|
| /**
|
| - * Parse a list of type arguments, starting at the [startToken], without
|
| - * actually creating a type argument list or changing the current token.
|
| - * Return the token following the type argument list that was parsed, or
|
| - * `null` if the given token is not the first token in a valid type argument
|
| - * list.
|
| - *
|
| - * This method must be kept in sync with [parseTypeArgumentList].
|
| + * Parse a postfix expression. Return the postfix expression that was parsed.
|
| *
|
| - * typeArguments ::=
|
| - * '<' typeList '>'
|
| + * postfixExpression ::=
|
| + * assignableExpression postfixOperator
|
| + * | primary selector*
|
| *
|
| - * typeList ::=
|
| - * type (',' type)*
|
| + * selector ::=
|
| + * assignableSelector
|
| + * | argumentList
|
| */
|
| - Token skipTypeArgumentList(Token startToken) {
|
| - Token token = startToken;
|
| - if (!_tokenMatches(token, TokenType.LT) &&
|
| - !_injectGenericCommentTypeList()) {
|
| - return null;
|
| - }
|
| - token = skipTypeName(token.next);
|
| - if (token == null) {
|
| - // If the start token '<' is followed by '>'
|
| - // then assume this should be type argument list but is missing a type
|
| - token = startToken.next;
|
| - if (_tokenMatches(token, TokenType.GT)) {
|
| - return token.next;
|
| - }
|
| - return null;
|
| - }
|
| - while (_tokenMatches(token, TokenType.COMMA)) {
|
| - token = skipTypeName(token.next);
|
| - if (token == null) {
|
| - return null;
|
| - }
|
| + Expression parsePostfixExpression() {
|
| + Expression operand = parseAssignableExpression(true);
|
| + TokenType type = _currentToken.type;
|
| + if (type == TokenType.OPEN_SQUARE_BRACKET ||
|
| + type == TokenType.PERIOD ||
|
| + type == TokenType.QUESTION_PERIOD ||
|
| + type == TokenType.OPEN_PAREN ||
|
| + (parseGenericMethods && type == TokenType.LT)) {
|
| + do {
|
| + if (_isLikelyArgumentList()) {
|
| + TypeArgumentList typeArguments = _parseOptionalTypeArguments();
|
| + ArgumentList argumentList = parseArgumentList();
|
| + Expression currentOperand = operand;
|
| + if (currentOperand is PropertyAccess) {
|
| + operand = new MethodInvocation(
|
| + currentOperand.target,
|
| + currentOperand.operator,
|
| + currentOperand.propertyName,
|
| + typeArguments,
|
| + argumentList);
|
| + } else {
|
| + operand = new FunctionExpressionInvocation(
|
| + operand, typeArguments, argumentList);
|
| + }
|
| + } else {
|
| + operand = parseAssignableSelector(operand, true);
|
| + }
|
| + type = _currentToken.type;
|
| + } while (type == TokenType.OPEN_SQUARE_BRACKET ||
|
| + type == TokenType.PERIOD ||
|
| + type == TokenType.QUESTION_PERIOD ||
|
| + type == TokenType.OPEN_PAREN);
|
| + return operand;
|
| }
|
| - if (token.type == TokenType.GT) {
|
| - return token.next;
|
| - } else if (token.type == TokenType.GT_GT) {
|
| - Token second = new Token(TokenType.GT, token.offset + 1);
|
| - second.setNextWithoutSettingPrevious(token.next);
|
| - return second;
|
| + if (!_currentToken.type.isIncrementOperator) {
|
| + return operand;
|
| }
|
| - return null;
|
| + _ensureAssignable(operand);
|
| + Token operator = getAndAdvance();
|
| + return new PostfixExpression(operand, operator);
|
| }
|
|
|
| /**
|
| - * Parse a type name, starting at the [startToken], without actually creating
|
| - * a type name or changing the current token. Return the token following the
|
| - * type name that was parsed, or `null` if the given token is not the first
|
| - * token in a valid type name.
|
| - *
|
| - * This method must be kept in sync with [parseTypeName].
|
| + * Parse a prefixed identifier. Return the prefixed identifier that was
|
| + * parsed.
|
| *
|
| - * type ::=
|
| - * qualified typeArguments?
|
| - */
|
| - Token skipTypeName(Token startToken) {
|
| - Token token = skipPrefixedIdentifier(startToken);
|
| - if (token == null) {
|
| - return null;
|
| - }
|
| - if (_tokenMatches(token, TokenType.LT)) {
|
| - token = skipTypeArgumentList(token);
|
| - }
|
| - return token;
|
| - }
|
| -
|
| - /**
|
| - * Advance to the next token in the token stream.
|
| + * prefixedIdentifier ::=
|
| + * identifier ('.' identifier)?
|
| */
|
| - void _advance() {
|
| - _currentToken = _currentToken.next;
|
| + Identifier parsePrefixedIdentifier() {
|
| + return _parsePrefixedIdentifierAfterIdentifier(parseSimpleIdentifier());
|
| }
|
|
|
| /**
|
| - * Append the character equivalent of the given [scalarValue] to the given
|
| - * [builder]. Use the [startIndex] and [endIndex] to report an error, and
|
| - * don't append anything to the builder, if the scalar value is invalid. The
|
| - * [escapeSequence] is the escape sequence that was parsed to produce the
|
| - * scalar value (used for error reporting).
|
| + * Parse a primary expression. Return the primary expression that was parsed.
|
| + *
|
| + * primary ::=
|
| + * thisExpression
|
| + * | 'super' unconditionalAssignableSelector
|
| + * | functionExpression
|
| + * | literal
|
| + * | identifier
|
| + * | newExpression
|
| + * | constObjectExpression
|
| + * | '(' expression ')'
|
| + * | argumentDefinitionTest
|
| + *
|
| + * literal ::=
|
| + * nullLiteral
|
| + * | booleanLiteral
|
| + * | numericLiteral
|
| + * | stringLiteral
|
| + * | symbolLiteral
|
| + * | mapLiteral
|
| + * | listLiteral
|
| */
|
| - void _appendScalarValue(StringBuffer buffer, String escapeSequence,
|
| - int scalarValue, int startIndex, int endIndex) {
|
| - if (scalarValue < 0 ||
|
| - scalarValue > Character.MAX_CODE_POINT ||
|
| - (scalarValue >= 0xD800 && scalarValue <= 0xDFFF)) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.INVALID_CODE_POINT, [escapeSequence]);
|
| - return;
|
| + Expression parsePrimaryExpression() {
|
| + if (_matchesIdentifier()) {
|
| + // TODO(brianwilkerson) The code below was an attempt to recover from an
|
| + // error case, but it needs to be applied as a recovery only after we
|
| + // know that parsing it as an identifier doesn't work. Leaving the code as
|
| + // a reminder of how to recover.
|
| +// if (isFunctionExpression(_peek())) {
|
| +// //
|
| +// // Function expressions were allowed to have names at one point, but this is now illegal.
|
| +// //
|
| +// reportError(ParserErrorCode.NAMED_FUNCTION_EXPRESSION, getAndAdvance());
|
| +// return parseFunctionExpression();
|
| +// }
|
| + return _parsePrefixedIdentifierUnchecked();
|
| }
|
| - if (scalarValue < Character.MAX_VALUE) {
|
| - buffer.writeCharCode(scalarValue);
|
| + TokenType type = _currentToken.type;
|
| + if (type == TokenType.STRING) {
|
| + return parseStringLiteral();
|
| + } else if (type == TokenType.INT) {
|
| + Token token = getAndAdvance();
|
| + int value = null;
|
| + try {
|
| + value = int.parse(token.lexeme);
|
| + } on FormatException {
|
| + // The invalid format should have been reported by the scanner.
|
| + }
|
| + return new IntegerLiteral(token, value);
|
| + }
|
| + Keyword keyword = _currentToken.keyword;
|
| + if (keyword == Keyword.NULL) {
|
| + return new NullLiteral(getAndAdvance());
|
| + } else if (keyword == Keyword.NEW) {
|
| + return parseNewExpression();
|
| + } else if (keyword == Keyword.THIS) {
|
| + return new ThisExpression(getAndAdvance());
|
| + } else if (keyword == Keyword.SUPER) {
|
| + // TODO(paulberry): verify with Gilad that "super" must be followed by
|
| + // unconditionalAssignableSelector in this case.
|
| + return parseAssignableSelector(
|
| + new SuperExpression(getAndAdvance()), false,
|
| + allowConditional: false);
|
| + } else if (keyword == Keyword.FALSE) {
|
| + return new BooleanLiteral(getAndAdvance(), false);
|
| + } else if (keyword == Keyword.TRUE) {
|
| + return new BooleanLiteral(getAndAdvance(), true);
|
| + }
|
| + if (type == TokenType.DOUBLE) {
|
| + Token token = getAndAdvance();
|
| + double value = 0.0;
|
| + try {
|
| + value = double.parse(token.lexeme);
|
| + } on FormatException {
|
| + // The invalid format should have been reported by the scanner.
|
| + }
|
| + return new DoubleLiteral(token, value);
|
| + } else if (type == TokenType.HEXADECIMAL) {
|
| + Token token = getAndAdvance();
|
| + int value = null;
|
| + try {
|
| + value = int.parse(token.lexeme.substring(2), radix: 16);
|
| + } on FormatException {
|
| + // The invalid format should have been reported by the scanner.
|
| + }
|
| + return new IntegerLiteral(token, value);
|
| + } else if (keyword == Keyword.CONST) {
|
| + return parseConstExpression();
|
| + } else if (type == TokenType.OPEN_PAREN) {
|
| + if (isFunctionExpression(_currentToken)) {
|
| + return parseFunctionExpression();
|
| + }
|
| + Token leftParenthesis = getAndAdvance();
|
| + bool wasInInitializer = _inInitializer;
|
| + _inInitializer = false;
|
| + try {
|
| + Expression expression = parseExpression2();
|
| + Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
|
| + return new ParenthesizedExpression(
|
| + leftParenthesis, expression, rightParenthesis);
|
| + } finally {
|
| + _inInitializer = wasInInitializer;
|
| + }
|
| + } else if (type == TokenType.LT || _injectGenericCommentTypeList()) {
|
| + return parseListOrMapLiteral(null);
|
| + } else if (type == TokenType.OPEN_CURLY_BRACKET) {
|
| + return parseMapLiteral(null, null);
|
| + } else if (type == TokenType.OPEN_SQUARE_BRACKET ||
|
| + type == TokenType.INDEX) {
|
| + return parseListLiteral(null, null);
|
| + } else if (type == TokenType.QUESTION &&
|
| + _tokenMatches(_peek(), TokenType.IDENTIFIER)) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.UNEXPECTED_TOKEN, [_currentToken.lexeme]);
|
| + _advance();
|
| + return parsePrimaryExpression();
|
| + } else if (keyword == Keyword.VOID) {
|
| + //
|
| + // Recover from having a return type of "void" where a return type is not
|
| + // expected.
|
| + //
|
| + // TODO(brianwilkerson) Improve this error message.
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.UNEXPECTED_TOKEN, [_currentToken.lexeme]);
|
| + _advance();
|
| + return parsePrimaryExpression();
|
| + } else if (type == TokenType.HASH) {
|
| + return parseSymbolLiteral();
|
| } else {
|
| - buffer.write(Character.toChars(scalarValue));
|
| + _reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
|
| + return createSyntheticIdentifier();
|
| }
|
| }
|
|
|
| /**
|
| - * Clone all token starting from the given [token] up to the end of the token
|
| - * stream, and return the first token in the new token stream.
|
| + * Parse a redirecting constructor invocation. The flag [hasPeriod] should be
|
| + * `true` if the `this` is followed by a period. Return the redirecting
|
| + * constructor invocation that was parsed.
|
| + *
|
| + * This method assumes that the current token matches `Keyword.THIS`.
|
| + *
|
| + * redirectingConstructorInvocation ::=
|
| + * 'this' ('.' identifier)? arguments
|
| */
|
| - Token _cloneTokens(Token token) {
|
| - if (token == null) {
|
| - return null;
|
| - }
|
| - token = token is CommentToken ? token.parent : token;
|
| - Token head = new Token(TokenType.EOF, -1);
|
| - head.setNext(head);
|
| - Token current = head;
|
| - while (token.type != TokenType.EOF) {
|
| - Token clone = token.copy();
|
| - current.setNext(clone);
|
| - current = clone;
|
| - token = token.next;
|
| + RedirectingConstructorInvocation parseRedirectingConstructorInvocation(
|
| + bool hasPeriod) {
|
| + Token keyword = getAndAdvance();
|
| + Token period = null;
|
| + SimpleIdentifier constructorName = null;
|
| + if (hasPeriod) {
|
| + period = getAndAdvance();
|
| + if (_matchesIdentifier()) {
|
| + constructorName = _parseSimpleIdentifierUnchecked(isDeclaration: false);
|
| + } else {
|
| + _reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
|
| + constructorName = createSyntheticIdentifier(isDeclaration: false);
|
| + _advance();
|
| + }
|
| }
|
| - Token tail = new Token(TokenType.EOF, 0);
|
| - tail.setNext(tail);
|
| - current.setNext(tail);
|
| - return head.next;
|
| + ArgumentList argumentList = _parseArgumentListChecked();
|
| + return new RedirectingConstructorInvocation(
|
| + keyword, period, constructorName, argumentList);
|
| }
|
|
|
| /**
|
| - * Convert the given [method] declaration into the nearest valid top-level
|
| - * function declaration (that is, the function declaration that most closely
|
| - * captures the components of the given method declaration).
|
| - */
|
| - FunctionDeclaration _convertToFunctionDeclaration(MethodDeclaration method) =>
|
| - new FunctionDeclaration(
|
| - method.documentationComment,
|
| - method.metadata,
|
| - method.externalKeyword,
|
| - method.returnType,
|
| - method.propertyKeyword,
|
| - method.name,
|
| - new FunctionExpression(
|
| - method.typeParameters, method.parameters, method.body));
|
| -
|
| - /**
|
| - * Return `true` if the current token could be the start of a compilation unit
|
| - * member. This method is used for recovery purposes to decide when to stop
|
| - * skipping tokens after finding an error while parsing a compilation unit
|
| - * member.
|
| + * Parse a relational expression. Return the relational expression that was
|
| + * parsed.
|
| + *
|
| + * relationalExpression ::=
|
| + * bitwiseOrExpression ('is' '!'? type | 'as' type | relationalOperator bitwiseOrExpression)?
|
| + * | 'super' relationalOperator bitwiseOrExpression
|
| */
|
| - bool _couldBeStartOfCompilationUnitMember() {
|
| + Expression parseRelationalExpression() {
|
| + if (_currentToken.keyword == Keyword.SUPER &&
|
| + _currentToken.next.type.isRelationalOperator) {
|
| + Expression expression = new SuperExpression(getAndAdvance());
|
| + Token operator = getAndAdvance();
|
| + return new BinaryExpression(
|
| + expression, operator, parseBitwiseOrExpression());
|
| + }
|
| + Expression expression = parseBitwiseOrExpression();
|
| Keyword keyword = _currentToken.keyword;
|
| - Token next = _currentToken.next;
|
| - TokenType nextType = next.type;
|
| - if ((keyword == Keyword.IMPORT ||
|
| - keyword == Keyword.EXPORT ||
|
| - keyword == Keyword.LIBRARY ||
|
| - keyword == Keyword.PART) &&
|
| - nextType != TokenType.PERIOD &&
|
| - nextType != TokenType.LT) {
|
| - // This looks like the start of a directive
|
| - return true;
|
| - } else if (keyword == Keyword.CLASS) {
|
| - // This looks like the start of a class definition
|
| - return true;
|
| - } else if (keyword == Keyword.TYPEDEF &&
|
| - nextType != TokenType.PERIOD &&
|
| - nextType != TokenType.LT) {
|
| - // This looks like the start of a typedef
|
| - return true;
|
| - } else if (keyword == Keyword.VOID ||
|
| - ((keyword == Keyword.GET || keyword == Keyword.SET) &&
|
| - _tokenMatchesIdentifier(next)) ||
|
| - (keyword == Keyword.OPERATOR && _isOperator(next))) {
|
| - // This looks like the start of a function
|
| - return true;
|
| - } else if (_matchesIdentifier()) {
|
| - if (nextType == TokenType.OPEN_PAREN) {
|
| - // This looks like the start of a function
|
| - return true;
|
| - }
|
| - Token token = skipReturnType(_currentToken);
|
| - if (token == null) {
|
| - return false;
|
| - }
|
| - // TODO(brianwilkerson) This looks wrong; should we be checking 'token'?
|
| - if (keyword == Keyword.GET ||
|
| - keyword == Keyword.SET ||
|
| - (keyword == Keyword.OPERATOR && _isOperator(next)) ||
|
| - _matchesIdentifier()) {
|
| - return true;
|
| + if (keyword == Keyword.AS) {
|
| + Token asOperator = getAndAdvance();
|
| + return new AsExpression(expression, asOperator, parseTypeName(true));
|
| + } else if (keyword == Keyword.IS) {
|
| + Token isOperator = getAndAdvance();
|
| + Token notOperator = null;
|
| + if (_matches(TokenType.BANG)) {
|
| + notOperator = getAndAdvance();
|
| }
|
| + return new IsExpression(
|
| + expression, isOperator, notOperator, parseTypeName(true));
|
| + } else if (_currentToken.type.isRelationalOperator) {
|
| + Token operator = getAndAdvance();
|
| + return new BinaryExpression(
|
| + expression, operator, parseBitwiseOrExpression());
|
| }
|
| - return false;
|
| + return expression;
|
| }
|
|
|
| /**
|
| - * Return a synthetic token representing the given [keyword].
|
| + * Parse a rethrow expression. Return the rethrow expression that was parsed.
|
| + *
|
| + * This method assumes that the current token matches `Keyword.RETHROW`.
|
| + *
|
| + * rethrowExpression ::=
|
| + * 'rethrow'
|
| */
|
| - Token _createSyntheticKeyword(Keyword keyword) => _injectToken(
|
| - new Parser_SyntheticKeywordToken(keyword, _currentToken.offset));
|
| + Expression parseRethrowExpression() => new RethrowExpression(getAndAdvance());
|
|
|
| /**
|
| - * Return a synthetic token with the given [type].
|
| + * Parse a return statement. Return the return statement that was parsed.
|
| + *
|
| + * This method assumes that the current token matches `Keyword.RETURN`.
|
| + *
|
| + * returnStatement ::=
|
| + * 'return' expression? ';'
|
| */
|
| - Token _createSyntheticToken(TokenType type) =>
|
| - _injectToken(new StringToken(type, "", _currentToken.offset));
|
| + Statement parseReturnStatement() {
|
| + Token returnKeyword = getAndAdvance();
|
| + if (_matches(TokenType.SEMICOLON)) {
|
| + return new ReturnStatement(returnKeyword, null, getAndAdvance());
|
| + }
|
| + Expression expression = parseExpression2();
|
| + Token semicolon = _expect(TokenType.SEMICOLON);
|
| + return new ReturnStatement(returnKeyword, expression, semicolon);
|
| + }
|
|
|
| /**
|
| - * Create and return a new token with the given [type]. The token will replace
|
| - * the first portion of the given [token], so it will have the same offset and
|
| - * will have any comments that might have preceeded the token.
|
| + * Parse a return type. Return the return type that was parsed.
|
| + *
|
| + * returnType ::=
|
| + * 'void'
|
| + * | type
|
| */
|
| - Token _createToken(Token token, TokenType type, {bool isBegin: false}) {
|
| - CommentToken comments = token.precedingComments;
|
| - if (comments == null) {
|
| - if (isBegin) {
|
| - return new BeginToken(type, token.offset);
|
| - }
|
| - return new Token(type, token.offset);
|
| - } else if (isBegin) {
|
| - return new BeginTokenWithComment(type, token.offset, comments);
|
| + TypeName parseReturnType() {
|
| + if (_currentToken.keyword == Keyword.VOID) {
|
| + return new TypeName(new SimpleIdentifier(getAndAdvance()), null);
|
| + } else {
|
| + return parseTypeName(false);
|
| }
|
| - return new TokenWithComment(type, token.offset, comments);
|
| }
|
|
|
| /**
|
| - * Check that the given [expression] is assignable and report an error if it
|
| - * isn't.
|
| + * Parse a setter. The [commentAndMetadata] is the documentation comment and
|
| + * metadata to be associated with the declaration. The [externalKeyword] is
|
| + * the 'external' token. The [staticKeyword] is the static keyword, or `null`
|
| + * if the setter is not static. The [returnType] is the return type that has
|
| + * already been parsed, or `null` if there was no return type. Return the
|
| + * setter that was parsed.
|
| *
|
| - * assignableExpression ::=
|
| - * primary (arguments* assignableSelector)+
|
| - * | 'super' unconditionalAssignableSelector
|
| - * | identifier
|
| + * This method assumes that the current token matches `Keyword.SET`.
|
| *
|
| - * unconditionalAssignableSelector ::=
|
| - * '[' expression ']'
|
| - * | '.' identifier
|
| + * setter ::=
|
| + * setterSignature functionBody?
|
| *
|
| - * assignableSelector ::=
|
| - * unconditionalAssignableSelector
|
| - * | '?.' identifier
|
| + * setterSignature ::=
|
| + * 'external'? 'static'? returnType? 'set' identifier formalParameterList
|
| */
|
| - void _ensureAssignable(Expression expression) {
|
| - if (expression != null && !expression.isAssignable) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.ILLEGAL_ASSIGNMENT_TO_NON_ASSIGNABLE);
|
| + MethodDeclaration parseSetter(CommentAndMetadata commentAndMetadata,
|
| + Token externalKeyword, Token staticKeyword, TypeName returnType) {
|
| + Token propertyKeyword = getAndAdvance();
|
| + SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
|
| + FormalParameterList parameters = parseFormalParameterList();
|
| + _validateFormalParameterList(parameters);
|
| + FunctionBody body = parseFunctionBody(
|
| + externalKeyword != null || staticKeyword == null,
|
| + ParserErrorCode.STATIC_SETTER_WITHOUT_BODY,
|
| + false);
|
| + if (externalKeyword != null && body is! EmptyFunctionBody) {
|
| + _reportErrorForCurrentToken(ParserErrorCode.EXTERNAL_SETTER_WITH_BODY);
|
| }
|
| + return new MethodDeclaration(
|
| + commentAndMetadata.comment,
|
| + commentAndMetadata.metadata,
|
| + externalKeyword,
|
| + staticKeyword,
|
| + returnType,
|
| + propertyKeyword,
|
| + null,
|
| + name,
|
| + null,
|
| + parameters,
|
| + body);
|
| }
|
|
|
| /**
|
| - * If the current token has the expected type, return it after advancing to
|
| - * the next token. Otherwise report an error and return the current token
|
| - * without advancing.
|
| - *
|
| - * Note that the method [_expectGt] should be used if the argument to this
|
| - * method would be [TokenType.GT].
|
| + * Parse a shift expression. Return the shift expression that was parsed.
|
| *
|
| - * The [type] is the type of token that is expected.
|
| + * shiftExpression ::=
|
| + * additiveExpression (shiftOperator additiveExpression)*
|
| + * | 'super' (shiftOperator additiveExpression)+
|
| */
|
| - Token _expect(TokenType type) {
|
| - if (_matches(type)) {
|
| - return getAndAdvance();
|
| + Expression parseShiftExpression() {
|
| + Expression expression;
|
| + if (_currentToken.keyword == Keyword.SUPER &&
|
| + _currentToken.next.type.isShiftOperator) {
|
| + expression = new SuperExpression(getAndAdvance());
|
| + } else {
|
| + expression = parseAdditiveExpression();
|
| }
|
| - // Remove uses of this method in favor of matches?
|
| - // Pass in the error code to use to report the error?
|
| - if (type == TokenType.SEMICOLON) {
|
| - if (_tokenMatches(_currentToken.next, TokenType.SEMICOLON)) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.UNEXPECTED_TOKEN, [_currentToken.lexeme]);
|
| - _advance();
|
| - return getAndAdvance();
|
| - }
|
| - _reportErrorForToken(ParserErrorCode.EXPECTED_TOKEN,
|
| - _currentToken.previous, [type.lexeme]);
|
| - return _createSyntheticToken(TokenType.SEMICOLON);
|
| + while (_currentToken.type.isShiftOperator) {
|
| + expression = new BinaryExpression(
|
| + expression, getAndAdvance(), parseAdditiveExpression());
|
| }
|
| - _reportErrorForCurrentToken(ParserErrorCode.EXPECTED_TOKEN, [type.lexeme]);
|
| - return _createSyntheticToken(type);
|
| + return expression;
|
| }
|
|
|
| /**
|
| - * If the current token has the type [TokenType.GT], return it after advancing
|
| - * to the next token. Otherwise report an error and create a synthetic token.
|
| + * Parse a simple identifier. Return the simple identifier that was parsed.
|
| + *
|
| + * identifier ::=
|
| + * IDENTIFIER
|
| */
|
| - Token _expectGt() {
|
| - if (_matchesGt()) {
|
| - return getAndAdvance();
|
| + SimpleIdentifier parseSimpleIdentifier({bool isDeclaration: false}) {
|
| + if (_matchesIdentifier()) {
|
| + return _parseSimpleIdentifierUnchecked(isDeclaration: isDeclaration);
|
| }
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.EXPECTED_TOKEN, [TokenType.GT.lexeme]);
|
| - return _createSyntheticToken(TokenType.GT);
|
| + _reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
|
| + return createSyntheticIdentifier(isDeclaration: isDeclaration);
|
| }
|
|
|
| /**
|
| - * If the current token is a keyword matching the given [keyword], return it
|
| - * after advancing to the next token. Otherwise report an error and return the
|
| - * current token without advancing.
|
| + * Parse a statement, starting with the given [token]. Return the statement
|
| + * that was parsed, or `null` if the tokens do not represent a recognizable
|
| + * statement.
|
| */
|
| - Token _expectKeyword(Keyword keyword) {
|
| - if (_matchesKeyword(keyword)) {
|
| - return getAndAdvance();
|
| + Statement parseStatement(Token token) {
|
| + _currentToken = token;
|
| + return parseStatement2();
|
| + }
|
| +
|
| + /**
|
| + * Parse a statement. Return the statement that was parsed.
|
| + *
|
| + * statement ::=
|
| + * label* nonLabeledStatement
|
| + */
|
| + Statement parseStatement2() {
|
| + List<Label> labels = null;
|
| + while (_matchesIdentifier() && _currentToken.next.type == TokenType.COLON) {
|
| + Label label = parseLabel(isDeclaration: true);
|
| + if (labels == null) {
|
| + labels = <Label>[label];
|
| + } else {
|
| + labels.add(label);
|
| + }
|
| }
|
| - // Remove uses of this method in favor of matches?
|
| - // Pass in the error code to use to report the error?
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.EXPECTED_TOKEN, [keyword.syntax]);
|
| - return _currentToken;
|
| + Statement statement = parseNonLabeledStatement();
|
| + if (labels == null) {
|
| + return statement;
|
| + }
|
| + return new LabeledStatement(labels, statement);
|
| }
|
|
|
| /**
|
| - * Search the given list of [ranges] for a range that contains the given
|
| - * [index]. Return the range that was found, or `null` if none of the ranges
|
| - * contain the index.
|
| + * Parse a sequence of statements, starting with the given [token]. Return the
|
| + * statements that were parsed, or `null` if the tokens do not represent a
|
| + * recognizable sequence of statements.
|
| */
|
| - List<int> _findRange(List<List<int>> ranges, int index) {
|
| - int rangeCount = ranges.length;
|
| - for (int i = 0; i < rangeCount; i++) {
|
| - List<int> range = ranges[i];
|
| - if (range[0] <= index && index <= range[1]) {
|
| - return range;
|
| - } else if (index < range[0]) {
|
| - return null;
|
| - }
|
| - }
|
| - return null;
|
| + List<Statement> parseStatements(Token token) {
|
| + _currentToken = token;
|
| + return _parseStatementList();
|
| }
|
|
|
| /**
|
| - * Return a list of the ranges of characters in the given [comment] that
|
| - * should be treated as code blocks.
|
| + * Parse a string literal. Return the string literal that was parsed.
|
| + *
|
| + * stringLiteral ::=
|
| + * MULTI_LINE_STRING+
|
| + * | SINGLE_LINE_STRING+
|
| */
|
| - List<List<int>> _getCodeBlockRanges(String comment) {
|
| - List<List<int>> ranges = <List<int>>[];
|
| - int length = comment.length;
|
| - if (length < 3) {
|
| - return ranges;
|
| - }
|
| - int index = 0;
|
| - int firstChar = comment.codeUnitAt(0);
|
| - if (firstChar == 0x2F) {
|
| - int secondChar = comment.codeUnitAt(1);
|
| - int thirdChar = comment.codeUnitAt(2);
|
| - if ((secondChar == 0x2A && thirdChar == 0x2A) ||
|
| - (secondChar == 0x2F && thirdChar == 0x2F)) {
|
| - index = 3;
|
| - }
|
| - }
|
| - if (StringUtilities.startsWith4(comment, index, 0x20, 0x20, 0x20, 0x20)) {
|
| - int end = index + 4;
|
| - while (end < length &&
|
| - comment.codeUnitAt(end) != 0xD &&
|
| - comment.codeUnitAt(end) != 0xA) {
|
| - end = end + 1;
|
| - }
|
| - ranges.add(<int>[index, end]);
|
| - index = end;
|
| - }
|
| - while (index < length) {
|
| - int currentChar = comment.codeUnitAt(index);
|
| - if (currentChar == 0xD || currentChar == 0xA) {
|
| - index = index + 1;
|
| - while (index < length &&
|
| - Character.isWhitespace(comment.codeUnitAt(index))) {
|
| - index = index + 1;
|
| - }
|
| - if (StringUtilities.startsWith6(
|
| - comment, index, 0x2A, 0x20, 0x20, 0x20, 0x20, 0x20)) {
|
| - int end = index + 6;
|
| - while (end < length &&
|
| - comment.codeUnitAt(end) != 0xD &&
|
| - comment.codeUnitAt(end) != 0xA) {
|
| - end = end + 1;
|
| - }
|
| - ranges.add(<int>[index, end]);
|
| - index = end;
|
| - }
|
| - } else if (index + 1 < length &&
|
| - currentChar == 0x5B &&
|
| - comment.codeUnitAt(index + 1) == 0x3A) {
|
| - int end = StringUtilities.indexOf2(comment, index + 2, 0x3A, 0x5D);
|
| - if (end < 0) {
|
| - end = length;
|
| - }
|
| - ranges.add(<int>[index, end]);
|
| - index = end + 1;
|
| - } else {
|
| - index = index + 1;
|
| - }
|
| + StringLiteral parseStringLiteral() {
|
| + if (_matches(TokenType.STRING)) {
|
| + return _parseStringLiteralUnchecked();
|
| }
|
| - return ranges;
|
| + _reportErrorForCurrentToken(ParserErrorCode.EXPECTED_STRING_LITERAL);
|
| + return createSyntheticStringLiteral();
|
| }
|
|
|
| /**
|
| - * Return the end token associated with the given [beginToken], or `null` if
|
| - * either the given token is not a begin token or it does not have an end
|
| - * token associated with it.
|
| + * Parse a super constructor invocation. Return the super constructor
|
| + * invocation that was parsed.
|
| + *
|
| + * This method assumes that the current token matches [Keyword.SUPER].
|
| + *
|
| + * superConstructorInvocation ::=
|
| + * 'super' ('.' identifier)? arguments
|
| */
|
| - Token _getEndToken(Token beginToken) {
|
| - if (beginToken is BeginToken) {
|
| - return beginToken.endToken;
|
| + SuperConstructorInvocation parseSuperConstructorInvocation() {
|
| + Token keyword = getAndAdvance();
|
| + Token period = null;
|
| + SimpleIdentifier constructorName = null;
|
| + if (_matches(TokenType.PERIOD)) {
|
| + period = getAndAdvance();
|
| + constructorName = parseSimpleIdentifier();
|
| }
|
| - return null;
|
| + ArgumentList argumentList = _parseArgumentListChecked();
|
| + return new SuperConstructorInvocation(
|
| + keyword, period, constructorName, argumentList);
|
| }
|
|
|
| - bool _injectGenericComment(TokenType type, int prefixLen) {
|
| - if (parseGenericMethodComments) {
|
| - CommentToken t = _currentToken.precedingComments;
|
| - for (; t != null; t = t.next) {
|
| - if (t.type == type) {
|
| - String comment = t.lexeme.substring(prefixLen, t.lexeme.length - 2);
|
| - Token list = _scanGenericMethodComment(comment, t.offset + prefixLen);
|
| - if (list != null) {
|
| - // Remove the token from the comment stream.
|
| - t.remove();
|
| - // Insert the tokens into the stream.
|
| - _injectTokenList(list);
|
| - return true;
|
| + /**
|
| + * Parse a switch statement. Return the switch statement that was parsed.
|
| + *
|
| + * switchStatement ::=
|
| + * 'switch' '(' expression ')' '{' switchCase* defaultCase? '}'
|
| + *
|
| + * switchCase ::=
|
| + * label* ('case' expression ':') statements
|
| + *
|
| + * defaultCase ::=
|
| + * label* 'default' ':' statements
|
| + */
|
| + SwitchStatement parseSwitchStatement() {
|
| + bool wasInSwitch = _inSwitch;
|
| + _inSwitch = true;
|
| + try {
|
| + HashSet<String> definedLabels = new HashSet<String>();
|
| + Token keyword = _expectKeyword(Keyword.SWITCH);
|
| + Token leftParenthesis = _expect(TokenType.OPEN_PAREN);
|
| + Expression expression = parseExpression2();
|
| + Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
|
| + Token leftBracket = _expect(TokenType.OPEN_CURLY_BRACKET);
|
| + Token defaultKeyword = null;
|
| + List<SwitchMember> members = <SwitchMember>[];
|
| + TokenType type = _currentToken.type;
|
| + while (type != TokenType.EOF && type != TokenType.CLOSE_CURLY_BRACKET) {
|
| + List<Label> labels = <Label>[];
|
| + while (
|
| + _matchesIdentifier() && _tokenMatches(_peek(), TokenType.COLON)) {
|
| + SimpleIdentifier identifier =
|
| + _parseSimpleIdentifierUnchecked(isDeclaration: true);
|
| + String label = identifier.token.lexeme;
|
| + if (definedLabels.contains(label)) {
|
| + _reportErrorForToken(
|
| + ParserErrorCode.DUPLICATE_LABEL_IN_SWITCH_STATEMENT,
|
| + identifier.token,
|
| + [label]);
|
| + } else {
|
| + definedLabels.add(label);
|
| + }
|
| + Token colon = getAndAdvance();
|
| + labels.add(new Label(identifier, colon));
|
| + }
|
| + Keyword keyword = _currentToken.keyword;
|
| + if (keyword == Keyword.CASE) {
|
| + Token caseKeyword = getAndAdvance();
|
| + Expression caseExpression = parseExpression2();
|
| + Token colon = _expect(TokenType.COLON);
|
| + members.add(new SwitchCase(labels, caseKeyword, caseExpression, colon,
|
| + _parseStatementList()));
|
| + if (defaultKeyword != null) {
|
| + _reportErrorForToken(
|
| + ParserErrorCode.SWITCH_HAS_CASE_AFTER_DEFAULT_CASE,
|
| + caseKeyword);
|
| + }
|
| + } else if (keyword == Keyword.DEFAULT) {
|
| + if (defaultKeyword != null) {
|
| + _reportErrorForToken(
|
| + ParserErrorCode.SWITCH_HAS_MULTIPLE_DEFAULT_CASES, _peek());
|
| + }
|
| + defaultKeyword = getAndAdvance();
|
| + Token colon = _expect(TokenType.COLON);
|
| + members.add(new SwitchDefault(
|
| + labels, defaultKeyword, colon, _parseStatementList()));
|
| + } else {
|
| + // We need to advance, otherwise we could end up in an infinite loop,
|
| + // but this could be a lot smarter about recovering from the error.
|
| + _reportErrorForCurrentToken(ParserErrorCode.EXPECTED_CASE_OR_DEFAULT);
|
| + bool atEndOrNextMember() {
|
| + TokenType type = _currentToken.type;
|
| + if (type == TokenType.EOF ||
|
| + type == TokenType.CLOSE_CURLY_BRACKET) {
|
| + return true;
|
| + }
|
| + Keyword keyword = _currentToken.keyword;
|
| + return keyword == Keyword.CASE || keyword == Keyword.DEFAULT;
|
| + }
|
| +
|
| + while (!atEndOrNextMember()) {
|
| + _advance();
|
| }
|
| }
|
| + type = _currentToken.type;
|
| }
|
| + Token rightBracket = _expect(TokenType.CLOSE_CURLY_BRACKET);
|
| + return new SwitchStatement(keyword, leftParenthesis, expression,
|
| + rightParenthesis, leftBracket, members, rightBracket);
|
| + } finally {
|
| + _inSwitch = wasInSwitch;
|
| }
|
| - return false;
|
| }
|
|
|
| /**
|
| - * Matches a generic comment type substitution and injects it into the token
|
| - * stream. Returns true if a match was injected, otherwise false.
|
| + * Parse a symbol literal. Return the symbol literal that was parsed.
|
| *
|
| - * These comments are of the form `/*=T*/`, in other words, a [TypeName]
|
| - * inside a slash-star comment, preceded by equals sign.
|
| + * This method assumes that the current token matches [TokenType.HASH].
|
| + *
|
| + * symbolLiteral ::=
|
| + * '#' identifier ('.' identifier)*
|
| */
|
| - bool _injectGenericCommentTypeAssign() {
|
| - return _injectGenericComment(TokenType.GENERIC_METHOD_TYPE_ASSIGN, 3);
|
| + SymbolLiteral parseSymbolLiteral() {
|
| + Token poundSign = getAndAdvance();
|
| + List<Token> components = <Token>[];
|
| + if (_matchesIdentifier()) {
|
| + components.add(getAndAdvance());
|
| + while (_optional(TokenType.PERIOD)) {
|
| + if (_matchesIdentifier()) {
|
| + components.add(getAndAdvance());
|
| + } else {
|
| + _reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
|
| + components.add(_createSyntheticToken(TokenType.IDENTIFIER));
|
| + break;
|
| + }
|
| + }
|
| + } else if (_currentToken.isOperator) {
|
| + components.add(getAndAdvance());
|
| + } else if (_matchesKeyword(Keyword.VOID)) {
|
| + components.add(getAndAdvance());
|
| + } else {
|
| + _reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
|
| + components.add(_createSyntheticToken(TokenType.IDENTIFIER));
|
| + }
|
| + return new SymbolLiteral(poundSign, components);
|
| }
|
|
|
| /**
|
| - * Matches a generic comment type parameters and injects them into the token
|
| - * stream. Returns true if a match was injected, otherwise false.
|
| + * Parse a throw expression. Return the throw expression that was parsed.
|
| *
|
| - * These comments are of the form `/*<K, V>*/`, in other words, a
|
| - * [TypeParameterList] or [TypeArgumentList] inside a slash-star comment.
|
| - */
|
| - bool _injectGenericCommentTypeList() {
|
| - return _injectGenericComment(TokenType.GENERIC_METHOD_TYPE_LIST, 2);
|
| - }
|
| -
|
| - /**
|
| - * Inject the given [token] into the token stream immediately before the
|
| - * current token.
|
| + * This method assumes that the current token matches [Keyword.THROW].
|
| + *
|
| + * throwExpression ::=
|
| + * 'throw' expression
|
| */
|
| - Token _injectToken(Token token) {
|
| - Token previous = _currentToken.previous;
|
| - token.setNext(_currentToken);
|
| - previous.setNext(token);
|
| - return token;
|
| - }
|
| -
|
| - void _injectTokenList(Token firstToken) {
|
| - // Scanner creates a cyclic EOF token.
|
| - Token lastToken = firstToken;
|
| - while (lastToken.next.type != TokenType.EOF) {
|
| - lastToken = lastToken.next;
|
| + Expression parseThrowExpression() {
|
| + Token keyword = getAndAdvance();
|
| + TokenType type = _currentToken.type;
|
| + if (type == TokenType.SEMICOLON || type == TokenType.CLOSE_PAREN) {
|
| + _reportErrorForToken(
|
| + ParserErrorCode.MISSING_EXPRESSION_IN_THROW, _currentToken);
|
| + return new ThrowExpression(keyword, createSyntheticIdentifier());
|
| }
|
| - // Inject these new tokens into the stream.
|
| - Token previous = _currentToken.previous;
|
| - lastToken.setNext(_currentToken);
|
| - previous.setNext(firstToken);
|
| - _currentToken = firstToken;
|
| + Expression expression = parseExpression2();
|
| + return new ThrowExpression(keyword, expression);
|
| }
|
|
|
| /**
|
| - * Return `true` if the current token could be the question mark in a
|
| - * condition expression. The current token is assumed to be a question mark.
|
| + * Parse a throw expression. Return the throw expression that was parsed.
|
| + *
|
| + * This method assumes that the current token matches [Keyword.THROW].
|
| + *
|
| + * throwExpressionWithoutCascade ::=
|
| + * 'throw' expressionWithoutCascade
|
| */
|
| - bool _isConditionalOperator() {
|
| - void parseOperation(Parser parser) {
|
| - parser.parseExpressionWithoutCascade();
|
| - }
|
| -
|
| - Token token = _skip(_currentToken.next, parseOperation);
|
| - if (token == null || !_tokenMatches(token, TokenType.COLON)) {
|
| - return false;
|
| + Expression parseThrowExpressionWithoutCascade() {
|
| + Token keyword = getAndAdvance();
|
| + TokenType type = _currentToken.type;
|
| + if (type == TokenType.SEMICOLON || type == TokenType.CLOSE_PAREN) {
|
| + _reportErrorForToken(
|
| + ParserErrorCode.MISSING_EXPRESSION_IN_THROW, _currentToken);
|
| + return new ThrowExpression(keyword, createSyntheticIdentifier());
|
| }
|
| - token = _skip(token.next, parseOperation);
|
| - return token != null;
|
| + Expression expression = parseExpressionWithoutCascade();
|
| + return new ThrowExpression(keyword, expression);
|
| }
|
|
|
| /**
|
| - * Return `true` if the given [character] is a valid hexadecimal digit.
|
| + * Parse a try statement. Return the try statement that was parsed.
|
| + *
|
| + * This method assumes that the current token matches [Keyword.TRY].
|
| + *
|
| + * tryStatement ::=
|
| + * 'try' block (onPart+ finallyPart? | finallyPart)
|
| + *
|
| + * onPart ::=
|
| + * catchPart block
|
| + * | 'on' type catchPart? block
|
| + *
|
| + * catchPart ::=
|
| + * 'catch' '(' identifier (',' identifier)? ')'
|
| + *
|
| + * finallyPart ::=
|
| + * 'finally' block
|
| */
|
| - bool _isHexDigit(int character) =>
|
| - (0x30 <= character && character <= 0x39) ||
|
| - (0x41 <= character && character <= 0x46) ||
|
| - (0x61 <= character && character <= 0x66);
|
| -
|
| - bool _isLikelyArgumentList() {
|
| - // Try to reduce the amount of lookahead required here before enabling
|
| - // generic methods.
|
| - if (_matches(TokenType.OPEN_PAREN)) {
|
| - return true;
|
| + Statement parseTryStatement() {
|
| + Token tryKeyword = getAndAdvance();
|
| + Block body = _parseBlockChecked();
|
| + List<CatchClause> catchClauses = <CatchClause>[];
|
| + Block finallyClause = null;
|
| + while (_matchesString(_ON) || _matchesKeyword(Keyword.CATCH)) {
|
| + Token onKeyword = null;
|
| + TypeName exceptionType = null;
|
| + if (_matchesString(_ON)) {
|
| + onKeyword = getAndAdvance();
|
| + exceptionType = parseTypeName(false);
|
| + }
|
| + Token catchKeyword = null;
|
| + Token leftParenthesis = null;
|
| + SimpleIdentifier exceptionParameter = null;
|
| + Token comma = null;
|
| + SimpleIdentifier stackTraceParameter = null;
|
| + Token rightParenthesis = null;
|
| + if (_matchesKeyword(Keyword.CATCH)) {
|
| + catchKeyword = getAndAdvance();
|
| + leftParenthesis = _expect(TokenType.OPEN_PAREN);
|
| + exceptionParameter = parseSimpleIdentifier(isDeclaration: true);
|
| + if (_matches(TokenType.COMMA)) {
|
| + comma = getAndAdvance();
|
| + stackTraceParameter = parseSimpleIdentifier(isDeclaration: true);
|
| + }
|
| + rightParenthesis = _expect(TokenType.CLOSE_PAREN);
|
| + }
|
| + Block catchBody = _parseBlockChecked();
|
| + catchClauses.add(new CatchClause(
|
| + onKeyword,
|
| + exceptionType,
|
| + catchKeyword,
|
| + leftParenthesis,
|
| + exceptionParameter,
|
| + comma,
|
| + stackTraceParameter,
|
| + rightParenthesis,
|
| + catchBody));
|
| }
|
| - if (!parseGenericMethods) {
|
| - return false;
|
| + Token finallyKeyword = null;
|
| + if (_matchesKeyword(Keyword.FINALLY)) {
|
| + finallyKeyword = getAndAdvance();
|
| + finallyClause = _parseBlockChecked();
|
| + } else if (catchClauses.isEmpty) {
|
| + _reportErrorForCurrentToken(ParserErrorCode.MISSING_CATCH_OR_FINALLY);
|
| }
|
| - Token token = skipTypeArgumentList(_currentToken);
|
| - return token != null && _tokenMatches(token, TokenType.OPEN_PAREN);
|
| + return new TryStatement(
|
| + tryKeyword, body, catchClauses, finallyKeyword, finallyClause);
|
| }
|
|
|
| /**
|
| - * Given that we have just found bracketed text within the given [comment],
|
| - * look to see whether that text is (a) followed by a parenthesized link
|
| - * address, (b) followed by a colon, or (c) followed by optional whitespace
|
| - * and another square bracket. The [rightIndex] is the index of the right
|
| - * bracket. Return `true` if the bracketed text is followed by a link address.
|
| + * Parse a type alias. The [commentAndMetadata] is the metadata to be
|
| + * associated with the member. Return the type alias that was parsed.
|
| *
|
| - * This method uses the syntax described by the
|
| - * <a href="http://daringfireball.net/projects/markdown/syntax">markdown</a>
|
| - * project.
|
| + * This method assumes that the current token matches [Keyword.TYPEDEF].
|
| + *
|
| + * typeAlias ::=
|
| + * 'typedef' typeAliasBody
|
| + *
|
| + * typeAliasBody ::=
|
| + * classTypeAlias
|
| + * | functionTypeAlias
|
| + *
|
| + * classTypeAlias ::=
|
| + * identifier typeParameters? '=' 'abstract'? mixinApplication
|
| + *
|
| + * mixinApplication ::=
|
| + * qualified withClause implementsClause? ';'
|
| + *
|
| + * functionTypeAlias ::=
|
| + * functionPrefix typeParameterList? formalParameterList ';'
|
| + *
|
| + * functionPrefix ::=
|
| + * returnType? name
|
| */
|
| - bool _isLinkText(String comment, int rightIndex) {
|
| - int length = comment.length;
|
| - int index = rightIndex + 1;
|
| - if (index >= length) {
|
| - return false;
|
| - }
|
| - int nextChar = comment.codeUnitAt(index);
|
| - if (nextChar == 0x28 || nextChar == 0x3A) {
|
| - return true;
|
| - }
|
| - while (Character.isWhitespace(nextChar)) {
|
| - index = index + 1;
|
| - if (index >= length) {
|
| - return false;
|
| + TypeAlias parseTypeAlias(CommentAndMetadata commentAndMetadata) {
|
| + Token keyword = getAndAdvance();
|
| + if (_matchesIdentifier()) {
|
| + Token next = _peek();
|
| + if (_tokenMatches(next, TokenType.LT)) {
|
| + next = _skipTypeParameterList(next);
|
| + if (next != null && _tokenMatches(next, TokenType.EQ)) {
|
| + TypeAlias typeAlias =
|
| + parseClassTypeAlias(commentAndMetadata, null, keyword);
|
| + _reportErrorForToken(
|
| + ParserErrorCode.DEPRECATED_CLASS_TYPE_ALIAS, keyword);
|
| + return typeAlias;
|
| + }
|
| + } else if (_tokenMatches(next, TokenType.EQ)) {
|
| + TypeAlias typeAlias =
|
| + parseClassTypeAlias(commentAndMetadata, null, keyword);
|
| + _reportErrorForToken(
|
| + ParserErrorCode.DEPRECATED_CLASS_TYPE_ALIAS, keyword);
|
| + return typeAlias;
|
| }
|
| - nextChar = comment.codeUnitAt(index);
|
| }
|
| - return nextChar == 0x5B;
|
| + return _parseFunctionTypeAlias(commentAndMetadata, keyword);
|
| }
|
|
|
| /**
|
| - * Return `true` if the given [startToken] appears to be the beginning of an
|
| - * operator declaration.
|
| + * Parse a list of type arguments. Return the type argument list that was
|
| + * parsed.
|
| + *
|
| + * This method assumes that the current token matches `TokenType.LT`.
|
| + *
|
| + * typeArguments ::=
|
| + * '<' typeList '>'
|
| + *
|
| + * typeList ::=
|
| + * type (',' type)*
|
| */
|
| - bool _isOperator(Token startToken) {
|
| - // Accept any operator here, even if it is not user definable.
|
| - if (!startToken.isOperator) {
|
| - return false;
|
| - }
|
| - // Token "=" means that it is actually a field initializer.
|
| - if (startToken.type == TokenType.EQ) {
|
| - return false;
|
| - }
|
| - // Consume all operator tokens.
|
| - Token token = startToken.next;
|
| - while (token.isOperator) {
|
| - token = token.next;
|
| + TypeArgumentList parseTypeArgumentList() {
|
| + Token leftBracket = getAndAdvance();
|
| + List<TypeName> arguments = <TypeName>[parseTypeName(false)];
|
| + while (_optional(TokenType.COMMA)) {
|
| + arguments.add(parseTypeName(false));
|
| }
|
| - // Formal parameter list is expect now.
|
| - return _tokenMatches(token, TokenType.OPEN_PAREN);
|
| + Token rightBracket = _expectGt();
|
| + return new TypeArgumentList(leftBracket, arguments, rightBracket);
|
| }
|
|
|
| - bool _isPeekGenericTypeParametersAndOpenParen() {
|
| - if (!parseGenericMethods) {
|
| - return false;
|
| - }
|
| - Token token = _skipTypeParameterList(_peek());
|
| - return token != null && _tokenMatches(token, TokenType.OPEN_PAREN);
|
| + /**
|
| + * Parse a type name. Return the type name that was parsed.
|
| + *
|
| + * type ::=
|
| + * qualified typeArguments?
|
| + */
|
| + TypeName parseTypeName(bool inExpression) {
|
| + TypeName realType = _parseTypeName(inExpression);
|
| + // If this is followed by a generic method type comment, allow the comment
|
| + // type to replace the real type name.
|
| + // TODO(jmesserly): this feels like a big hammer. Can we restrict it to
|
| + // only work inside generic methods?
|
| + TypeName typeFromComment = _parseOptionalTypeNameComment();
|
| + return typeFromComment ?? realType;
|
| }
|
|
|
| /**
|
| - * Return `true` if the [startToken] appears to be the first token of a type
|
| - * name that is followed by a variable or field formal parameter.
|
| + * Parse a type parameter. Return the type parameter that was parsed.
|
| + *
|
| + * typeParameter ::=
|
| + * metadata name ('extends' bound)?
|
| */
|
| - bool _isTypedIdentifier(Token startToken) {
|
| - Token token = skipReturnType(startToken);
|
| - if (token == null) {
|
| - return false;
|
| - } else if (_tokenMatchesIdentifier(token)) {
|
| - return true;
|
| - } else if (_tokenMatchesKeyword(token, Keyword.THIS) &&
|
| - _tokenMatches(token.next, TokenType.PERIOD) &&
|
| - _tokenMatchesIdentifier(token.next.next)) {
|
| - return true;
|
| - } else if (_tokenMatchesKeyword(startToken, Keyword.VOID)) {
|
| - // The keyword 'void' isn't a valid identifier, so it should be assumed to
|
| - // be a type name.
|
| - return true;
|
| - } else if (startToken.next != token &&
|
| - !_tokenMatches(token, TokenType.OPEN_PAREN)) {
|
| - // The type is more than a simple identifier, so it should be assumed to
|
| - // be a type name.
|
| - return true;
|
| + TypeParameter parseTypeParameter() {
|
| + CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
|
| + SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
|
| + if (_matchesKeyword(Keyword.EXTENDS)) {
|
| + Token keyword = getAndAdvance();
|
| + TypeName bound = parseTypeName(false);
|
| + return new TypeParameter(commentAndMetadata.comment,
|
| + commentAndMetadata.metadata, name, keyword, bound);
|
| }
|
| - return false;
|
| + return new TypeParameter(commentAndMetadata.comment,
|
| + commentAndMetadata.metadata, name, null, null);
|
| }
|
|
|
| /**
|
| - * Increments the error reporting lock level. If level is more than `0`, then
|
| - * [reportError] wont report any error.
|
| + * Parse a list of type parameters. Return the list of type parameters that
|
| + * were parsed.
|
| + *
|
| + * This method assumes that the current token matches `TokenType.LT`.
|
| + *
|
| + * typeParameterList ::=
|
| + * '<' typeParameter (',' typeParameter)* '>'
|
| */
|
| - void _lockErrorListener() {
|
| - _errorListenerLock++;
|
| + TypeParameterList parseTypeParameterList() {
|
| + Token leftBracket = getAndAdvance();
|
| + List<TypeParameter> typeParameters = <TypeParameter>[parseTypeParameter()];
|
| + while (_optional(TokenType.COMMA)) {
|
| + typeParameters.add(parseTypeParameter());
|
| + }
|
| + Token rightBracket = _expectGt();
|
| + return new TypeParameterList(leftBracket, typeParameters, rightBracket);
|
| }
|
|
|
| /**
|
| - * Return `true` if the current token has the given [type]. Note that the
|
| - * method [_matchesGt] should be used if the argument to this method would be
|
| - * [TokenType.GT].
|
| + * Parse a unary expression. Return the unary expression that was parsed.
|
| + *
|
| + * unaryExpression ::=
|
| + * prefixOperator unaryExpression
|
| + * | awaitExpression
|
| + * | postfixExpression
|
| + * | unaryOperator 'super'
|
| + * | '-' 'super'
|
| + * | incrementOperator assignableExpression
|
| */
|
| - bool _matches(TokenType type) => _currentToken.type == type;
|
| + Expression parseUnaryExpression() {
|
| + TokenType type = _currentToken.type;
|
| + if (type == TokenType.MINUS ||
|
| + type == TokenType.BANG ||
|
| + type == TokenType.TILDE) {
|
| + Token operator = getAndAdvance();
|
| + if (_matchesKeyword(Keyword.SUPER)) {
|
| + TokenType nextType = _peek().type;
|
| + if (nextType == TokenType.OPEN_SQUARE_BRACKET ||
|
| + nextType == TokenType.PERIOD) {
|
| + // "prefixOperator unaryExpression"
|
| + // --> "prefixOperator postfixExpression"
|
| + // --> "prefixOperator primary selector*"
|
| + // --> "prefixOperator 'super' assignableSelector selector*"
|
| + return new PrefixExpression(operator, parseUnaryExpression());
|
| + }
|
| + return new PrefixExpression(
|
| + operator, new SuperExpression(getAndAdvance()));
|
| + }
|
| + return new PrefixExpression(operator, parseUnaryExpression());
|
| + } else if (_currentToken.type.isIncrementOperator) {
|
| + Token operator = getAndAdvance();
|
| + if (_matchesKeyword(Keyword.SUPER)) {
|
| + TokenType nextType = _peek().type;
|
| + if (nextType == TokenType.OPEN_SQUARE_BRACKET ||
|
| + nextType == TokenType.PERIOD) {
|
| + // --> "prefixOperator 'super' assignableSelector selector*"
|
| + return new PrefixExpression(operator, parseUnaryExpression());
|
| + }
|
| + //
|
| + // Even though it is not valid to use an incrementing operator
|
| + // ('++' or '--') before 'super', we can (and therefore must) interpret
|
| + // "--super" as semantically equivalent to "-(-super)". Unfortunately,
|
| + // we cannot do the same for "++super" because "+super" is also not
|
| + // valid.
|
| + //
|
| + if (type == TokenType.MINUS_MINUS) {
|
| + Token firstOperator = _createToken(operator, TokenType.MINUS);
|
| + Token secondOperator =
|
| + new Token(TokenType.MINUS, operator.offset + 1);
|
| + secondOperator.setNext(_currentToken);
|
| + firstOperator.setNext(secondOperator);
|
| + operator.previous.setNext(firstOperator);
|
| + return new PrefixExpression(
|
| + firstOperator,
|
| + new PrefixExpression(
|
| + secondOperator, new SuperExpression(getAndAdvance())));
|
| + }
|
| + // Invalid operator before 'super'
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.INVALID_OPERATOR_FOR_SUPER, [operator.lexeme]);
|
| + return new PrefixExpression(
|
| + operator, new SuperExpression(getAndAdvance()));
|
| + }
|
| + return new PrefixExpression(
|
| + operator, _parseAssignableExpressionNotStartingWithSuper(false));
|
| + } else if (type == TokenType.PLUS) {
|
| + _reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
|
| + return createSyntheticIdentifier();
|
| + } else if (_inAsync && _matchesString(_AWAIT)) {
|
| + return parseAwaitExpression();
|
| + }
|
| + return parsePostfixExpression();
|
| + }
|
|
|
| /**
|
| - * Return `true` if the current token has a type of [TokenType.GT]. Note that
|
| - * this method, unlike other variants, will modify the token stream if
|
| - * possible to match desired type. In particular, if the next token is either
|
| - * a '>>' or '>>>', the token stream will be re-written and `true` will be
|
| - * returned.
|
| + * Parse a variable declaration. Return the variable declaration that was
|
| + * parsed.
|
| + *
|
| + * variableDeclaration ::=
|
| + * identifier ('=' expression)?
|
| */
|
| - bool _matchesGt() {
|
| - TokenType currentType = _currentToken.type;
|
| - if (currentType == TokenType.GT) {
|
| - return true;
|
| - } else if (currentType == TokenType.GT_GT) {
|
| - Token first = _createToken(_currentToken, TokenType.GT);
|
| - Token second = new Token(TokenType.GT, _currentToken.offset + 1);
|
| - second.setNext(_currentToken.next);
|
| - first.setNext(second);
|
| - _currentToken.previous.setNext(first);
|
| - _currentToken = first;
|
| - return true;
|
| - } else if (currentType == TokenType.GT_EQ) {
|
| - Token first = _createToken(_currentToken, TokenType.GT);
|
| - Token second = new Token(TokenType.EQ, _currentToken.offset + 1);
|
| - second.setNext(_currentToken.next);
|
| - first.setNext(second);
|
| - _currentToken.previous.setNext(first);
|
| - _currentToken = first;
|
| - return true;
|
| - } else if (currentType == TokenType.GT_GT_EQ) {
|
| - int offset = _currentToken.offset;
|
| - Token first = _createToken(_currentToken, TokenType.GT);
|
| - Token second = new Token(TokenType.GT, offset + 1);
|
| - Token third = new Token(TokenType.EQ, offset + 2);
|
| - third.setNext(_currentToken.next);
|
| - second.setNext(third);
|
| - first.setNext(second);
|
| - _currentToken.previous.setNext(first);
|
| - _currentToken = first;
|
| - return true;
|
| + VariableDeclaration parseVariableDeclaration() {
|
| + // TODO(paulberry): prior to the fix for bug 23204, we permitted
|
| + // annotations before variable declarations (e.g. "String @deprecated s;").
|
| + // Although such constructions are prohibited by the spec, we may want to
|
| + // consider handling them anyway to allow for better parser recovery in the
|
| + // event that the user erroneously tries to use them. However, as a
|
| + // counterargument, this would likely degrade parser recovery in the event
|
| + // of a construct like "class C { int @deprecated foo() {} }" (i.e. the
|
| + // user is in the middle of inserting "int bar;" prior to
|
| + // "@deprecated foo() {}").
|
| + SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
|
| + Token equals = null;
|
| + Expression initializer = null;
|
| + if (_matches(TokenType.EQ)) {
|
| + equals = getAndAdvance();
|
| + initializer = parseExpression2();
|
| }
|
| - return false;
|
| + return new VariableDeclaration(name, equals, initializer);
|
| }
|
|
|
| /**
|
| - * Return `true` if the current token is a valid identifier. Valid identifiers
|
| - * include built-in identifiers (pseudo-keywords).
|
| + * Parse a variable declaration list. The [commentAndMetadata] is the metadata
|
| + * to be associated with the variable declaration list. Return the variable
|
| + * declaration list that was parsed.
|
| + *
|
| + * variableDeclarationList ::=
|
| + * finalConstVarOrType variableDeclaration (',' variableDeclaration)*
|
| */
|
| - bool _matchesIdentifier() => _tokenMatchesIdentifier(_currentToken);
|
| + VariableDeclarationList parseVariableDeclarationListAfterMetadata(
|
| + CommentAndMetadata commentAndMetadata) {
|
| + FinalConstVarOrType holder = parseFinalConstVarOrType(false);
|
| + return parseVariableDeclarationListAfterType(
|
| + commentAndMetadata, holder.keyword, holder.type);
|
| + }
|
|
|
| /**
|
| - * Return `true` if the current token matches the given [keyword].
|
| + * Parse a variable declaration list. The [commentAndMetadata] is the metadata
|
| + * to be associated with the variable declaration list, or `null` if there is
|
| + * no attempt at parsing the comment and metadata. The [keyword] is the token
|
| + * representing the 'final', 'const' or 'var' keyword, or `null` if there is
|
| + * no keyword. The [type] is the type of the variables in the list. Return the
|
| + * variable declaration list that was parsed.
|
| + *
|
| + * variableDeclarationList ::=
|
| + * finalConstVarOrType variableDeclaration (',' variableDeclaration)*
|
| */
|
| - bool _matchesKeyword(Keyword keyword) =>
|
| - _tokenMatchesKeyword(_currentToken, keyword);
|
| + VariableDeclarationList parseVariableDeclarationListAfterType(
|
| + CommentAndMetadata commentAndMetadata, Token keyword, TypeName type) {
|
| + if (type != null &&
|
| + keyword != null &&
|
| + _tokenMatchesKeyword(keyword, Keyword.VAR)) {
|
| + _reportErrorForToken(ParserErrorCode.VAR_AND_TYPE, keyword);
|
| + }
|
| + List<VariableDeclaration> variables = <VariableDeclaration>[
|
| + parseVariableDeclaration()
|
| + ];
|
| + while (_optional(TokenType.COMMA)) {
|
| + variables.add(parseVariableDeclaration());
|
| + }
|
| + return new VariableDeclarationList(commentAndMetadata?.comment,
|
| + commentAndMetadata?.metadata, keyword, type, variables);
|
| + }
|
|
|
| /**
|
| - * Return `true` if the current token matches the given [identifier].
|
| - */
|
| - bool _matchesString(String identifier) =>
|
| - _currentToken.type == TokenType.IDENTIFIER &&
|
| - _currentToken.lexeme == identifier;
|
| + * Parse a variable declaration statement. The [commentAndMetadata] is the
|
| + * metadata to be associated with the variable declaration statement, or
|
| + * `null` if there is no attempt at parsing the comment and metadata. Return
|
| + * the variable declaration statement that was parsed.
|
| + *
|
| + * variableDeclarationStatement ::=
|
| + * variableDeclarationList ';'
|
| + */
|
| + VariableDeclarationStatement parseVariableDeclarationStatementAfterMetadata(
|
| + CommentAndMetadata commentAndMetadata) {
|
| + // Token startToken = currentToken;
|
| + VariableDeclarationList variableList =
|
| + parseVariableDeclarationListAfterMetadata(commentAndMetadata);
|
| +// if (!matches(TokenType.SEMICOLON)) {
|
| +// if (matches(startToken, Keyword.VAR) && isTypedIdentifier(startToken.getNext())) {
|
| +// // TODO(brianwilkerson) This appears to be of the form "var type variable". We should do
|
| +// // a better job of recovering in this case.
|
| +// }
|
| +// }
|
| + Token semicolon = _expect(TokenType.SEMICOLON);
|
| + return new VariableDeclarationStatement(variableList, semicolon);
|
| + }
|
|
|
| /**
|
| - * If the current token has the given [type], then advance to the next token
|
| - * and return `true`. Otherwise, return `false` without advancing. This method
|
| - * should not be invoked with an argument value of [TokenType.GT].
|
| + * Parse a while statement. Return the while statement that was parsed.
|
| + *
|
| + * This method assumes that the current token matches [Keyword.WHILE].
|
| + *
|
| + * whileStatement ::=
|
| + * 'while' '(' expression ')' statement
|
| */
|
| - bool _optional(TokenType type) {
|
| - if (_currentToken.type == type) {
|
| - _advance();
|
| - return true;
|
| + Statement parseWhileStatement() {
|
| + bool wasInLoop = _inLoop;
|
| + _inLoop = true;
|
| + try {
|
| + Token keyword = getAndAdvance();
|
| + Token leftParenthesis = _expect(TokenType.OPEN_PAREN);
|
| + Expression condition = parseExpression2();
|
| + Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
|
| + Statement body = parseStatement2();
|
| + return new WhileStatement(
|
| + keyword, leftParenthesis, condition, rightParenthesis, body);
|
| + } finally {
|
| + _inLoop = wasInLoop;
|
| }
|
| - return false;
|
| }
|
|
|
| /**
|
| - * Parse an argument list when we need to check for an open paren and recover
|
| - * when there isn't one. Return the argument list that was parsed.
|
| + * Parse a with clause. Return the with clause that was parsed.
|
| + *
|
| + * This method assumes that the current token matches `Keyword.WITH`.
|
| + *
|
| + * withClause ::=
|
| + * 'with' typeName (',' typeName)*
|
| */
|
| - ArgumentList _parseArgumentListChecked() {
|
| - if (_matches(TokenType.OPEN_PAREN)) {
|
| - return parseArgumentList();
|
| + WithClause parseWithClause() {
|
| + Token withKeyword = getAndAdvance();
|
| + List<TypeName> types = <TypeName>[parseTypeName(false)];
|
| + while (_optional(TokenType.COMMA)) {
|
| + types.add(parseTypeName(false));
|
| }
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.EXPECTED_TOKEN, [TokenType.OPEN_PAREN.lexeme]);
|
| - // Recovery: Look to see whether there is a close paren that isn't matched
|
| - // to an open paren and if so parse the list of arguments as normal.
|
| - return new ArgumentList(_createSyntheticToken(TokenType.OPEN_PAREN), null,
|
| - _createSyntheticToken(TokenType.CLOSE_PAREN));
|
| + return new WithClause(withKeyword, types);
|
| }
|
|
|
| /**
|
| - * Parse an assert within a constructor's initializer list. Return the assert.
|
| + * Parse a yield statement. Return the yield statement that was parsed.
|
| *
|
| - * This method assumes that the current token matches `Keyword.ASSERT`.
|
| + * This method assumes that the current token matches [Keyword.YIELD].
|
| *
|
| - * assertInitializer ::=
|
| - * 'assert' '(' expression [',' expression] ')'
|
| + * yieldStatement ::=
|
| + * 'yield' '*'? expression ';'
|
| */
|
| - void _parseAssertInitializer() {
|
| - // TODO(brianwilkerson) Capture the syntax in the AST using a new class,
|
| - // such as AssertInitializer
|
| - Token keyword = getAndAdvance();
|
| - Token leftParen = _expect(TokenType.OPEN_PAREN);
|
| - Expression expression = parseExpression2();
|
| - Token comma;
|
| - Expression message;
|
| - if (_matches(TokenType.COMMA)) {
|
| - comma = getAndAdvance();
|
| - message = parseExpression2();
|
| + YieldStatement parseYieldStatement() {
|
| + Token yieldToken = getAndAdvance();
|
| + Token star = null;
|
| + if (_matches(TokenType.STAR)) {
|
| + star = getAndAdvance();
|
| }
|
| - Token rightParen = _expect(TokenType.CLOSE_PAREN);
|
| -// return new AssertInitializer(
|
| -// keyword, leftParen, expression, comma, message, rightParen);
|
| + Expression expression = parseExpression2();
|
| + Token semicolon = _expect(TokenType.SEMICOLON);
|
| + return new YieldStatement(yieldToken, star, expression, semicolon);
|
| }
|
|
|
| /**
|
| - * Parse an assignable expression given that the current token is not 'super'.
|
| - * The [primaryAllowed] is `true` if the expression is allowed to be a primary
|
| - * without any assignable selector. Return the assignable expression that was
|
| - * parsed.
|
| + * Parse a prefixed identifier, starting at the [startToken], without actually
|
| + * creating a prefixed identifier or changing the current token. Return the
|
| + * token following the prefixed identifier that was parsed, or `null` if the
|
| + * given token is not the first token in a valid prefixed identifier.
|
| + *
|
| + * This method must be kept in sync with [parsePrefixedIdentifier].
|
| + *
|
| + * prefixedIdentifier ::=
|
| + * identifier ('.' identifier)?
|
| */
|
| - Expression _parseAssignableExpressionNotStartingWithSuper(
|
| - bool primaryAllowed) {
|
| - //
|
| - // A primary expression can start with an identifier. We resolve the
|
| - // ambiguity by determining whether the primary consists of anything other
|
| - // than an identifier and/or is followed by an assignableSelector.
|
| - //
|
| - Expression expression = parsePrimaryExpression();
|
| - bool isOptional = primaryAllowed || expression is SimpleIdentifier;
|
| - while (true) {
|
| - while (_isLikelyArgumentList()) {
|
| - TypeArgumentList typeArguments = _parseOptionalTypeArguments();
|
| - ArgumentList argumentList = parseArgumentList();
|
| - Expression currentExpression = expression;
|
| - if (currentExpression is SimpleIdentifier) {
|
| - expression = new MethodInvocation(
|
| - null, null, currentExpression, typeArguments, argumentList);
|
| - } else if (currentExpression is PrefixedIdentifier) {
|
| - expression = new MethodInvocation(
|
| - currentExpression.prefix,
|
| - currentExpression.period,
|
| - currentExpression.identifier,
|
| - typeArguments,
|
| - argumentList);
|
| - } else if (currentExpression is PropertyAccess) {
|
| - expression = new MethodInvocation(
|
| - currentExpression.target,
|
| - currentExpression.operator,
|
| - currentExpression.propertyName,
|
| - typeArguments,
|
| - argumentList);
|
| - } else {
|
| - expression = new FunctionExpressionInvocation(
|
| - expression, typeArguments, argumentList);
|
| - }
|
| - if (!primaryAllowed) {
|
| - isOptional = false;
|
| - }
|
| - }
|
| - Expression selectorExpression = _parseAssignableSelector(
|
| - expression, isOptional || (expression is PrefixedIdentifier));
|
| - if (identical(selectorExpression, expression)) {
|
| - if (!isOptional && (expression is PrefixedIdentifier)) {
|
| - PrefixedIdentifier identifier = expression as PrefixedIdentifier;
|
| - expression = new PropertyAccess(
|
| - identifier.prefix, identifier.period, identifier.identifier);
|
| - }
|
| - return expression;
|
| - }
|
| - expression = selectorExpression;
|
| - isOptional = true;
|
| + Token skipPrefixedIdentifier(Token startToken) {
|
| + Token token = skipSimpleIdentifier(startToken);
|
| + if (token == null) {
|
| + return null;
|
| + } else if (!_tokenMatches(token, TokenType.PERIOD)) {
|
| + return token;
|
| + }
|
| + token = token.next;
|
| + Token nextToken = skipSimpleIdentifier(token);
|
| + if (nextToken != null) {
|
| + return nextToken;
|
| + } else if (_tokenMatches(token, TokenType.CLOSE_PAREN) ||
|
| + _tokenMatches(token, TokenType.COMMA)) {
|
| + // If the `id.` is followed by something that cannot produce a valid
|
| + // structure then assume this is a prefixed identifier but missing the
|
| + // trailing identifier
|
| + return token;
|
| }
|
| + return null;
|
| }
|
|
|
| /**
|
| - * Parse an assignable selector. The [prefix] is the expression preceding the
|
| - * selector. The [optional] is `true` if the selector is optional. Return the
|
| - * assignable selector that was parsed, or the original prefix if there was no
|
| - * assignable selector. If [allowConditional] is false, then the '?.'
|
| - * operator will still be parsed, but a parse error will be generated.
|
| + * Parse a return type, starting at the [startToken], without actually
|
| + * creating a return type or changing the current token. Return the token
|
| + * following the return type that was parsed, or `null` if the given token is
|
| + * not the first token in a valid return type.
|
| *
|
| - * unconditionalAssignableSelector ::=
|
| - * '[' expression ']'
|
| - * | '.' identifier
|
| + * This method must be kept in sync with [parseReturnType].
|
| *
|
| - * assignableSelector ::=
|
| - * unconditionalAssignableSelector
|
| - * | '?.' identifier
|
| + * returnType ::=
|
| + * 'void'
|
| + * | type
|
| */
|
| - Expression _parseAssignableSelector(Expression prefix, bool optional,
|
| - {bool allowConditional: true}) {
|
| - TokenType type = _currentToken.type;
|
| - if (type == TokenType.OPEN_SQUARE_BRACKET) {
|
| - Token leftBracket = getAndAdvance();
|
| - bool wasInInitializer = _inInitializer;
|
| - _inInitializer = false;
|
| - try {
|
| - Expression index = parseExpression2();
|
| - Token rightBracket = _expect(TokenType.CLOSE_SQUARE_BRACKET);
|
| - return new IndexExpression.forTarget(
|
| - prefix, leftBracket, index, rightBracket);
|
| - } finally {
|
| - _inInitializer = wasInInitializer;
|
| - }
|
| + Token skipReturnType(Token startToken) {
|
| + if (_tokenMatchesKeyword(startToken, Keyword.VOID)) {
|
| + return startToken.next;
|
| } else {
|
| - bool isQuestionPeriod = type == TokenType.QUESTION_PERIOD;
|
| - if (type == TokenType.PERIOD || isQuestionPeriod) {
|
| - if (isQuestionPeriod && !allowConditional) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.INVALID_OPERATOR_FOR_SUPER,
|
| - [_currentToken.lexeme]);
|
| - }
|
| - Token operator = getAndAdvance();
|
| - return new PropertyAccess(prefix, operator, parseSimpleIdentifier());
|
| - } else {
|
| - if (!optional) {
|
| - // Report the missing selector.
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.MISSING_ASSIGNABLE_SELECTOR);
|
| - }
|
| - return prefix;
|
| - }
|
| + return skipTypeName(startToken);
|
| }
|
| }
|
|
|
| /**
|
| - * Parse a block when we need to check for an open curly brace and recover
|
| - * when there isn't one. Return the block that was parsed.
|
| + * Parse a simple identifier, starting at the [startToken], without actually
|
| + * creating a simple identifier or changing the current token. Return the
|
| + * token following the simple identifier that was parsed, or `null` if the
|
| + * given token is not the first token in a valid simple identifier.
|
| *
|
| - * block ::=
|
| - * '{' statements '}'
|
| + * This method must be kept in sync with [parseSimpleIdentifier].
|
| + *
|
| + * identifier ::=
|
| + * IDENTIFIER
|
| */
|
| - Block _parseBlockChecked() {
|
| - if (_matches(TokenType.OPEN_CURLY_BRACKET)) {
|
| - return parseBlock();
|
| - }
|
| - // TODO(brianwilkerson) Improve the error message.
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.EXPECTED_TOKEN, [TokenType.OPEN_CURLY_BRACKET.lexeme]);
|
| - // Recovery: Check for an unmatched closing curly bracket and parse
|
| - // statements until it is reached.
|
| - return new Block(_createSyntheticToken(TokenType.OPEN_CURLY_BRACKET), null,
|
| - _createSyntheticToken(TokenType.CLOSE_CURLY_BRACKET));
|
| + Token skipSimpleIdentifier(Token startToken) {
|
| + if (_tokenMatches(startToken, TokenType.IDENTIFIER) ||
|
| + _tokenMatchesPseudoKeyword(startToken)) {
|
| + return startToken.next;
|
| + }
|
| + return null;
|
| }
|
|
|
| /**
|
| - * Parse a list of class members. The [className] is the name of the class
|
| - * whose members are being parsed. The [closingBracket] is the closing bracket
|
| - * for the class, or `null` if the closing bracket is missing. Return the list
|
| - * of class members that were parsed.
|
| + * Parse a string literal, starting at the [startToken], without actually
|
| + * creating a string literal or changing the current token. Return the token
|
| + * following the string literal that was parsed, or `null` if the given token
|
| + * is not the first token in a valid string literal.
|
| *
|
| - * classMembers ::=
|
| - * (metadata memberDefinition)*
|
| + * This method must be kept in sync with [parseStringLiteral].
|
| + *
|
| + * stringLiteral ::=
|
| + * MULTI_LINE_STRING+
|
| + * | SINGLE_LINE_STRING+
|
| */
|
| - List<ClassMember> _parseClassMembers(String className, Token closingBracket) {
|
| - List<ClassMember> members = <ClassMember>[];
|
| - Token memberStart = _currentToken;
|
| - TokenType type = _currentToken.type;
|
| - Keyword keyword = _currentToken.keyword;
|
| - while (type != TokenType.EOF &&
|
| - type != TokenType.CLOSE_CURLY_BRACKET &&
|
| - (closingBracket != null ||
|
| - (keyword != Keyword.CLASS && keyword != Keyword.TYPEDEF))) {
|
| - if (type == TokenType.SEMICOLON) {
|
| - _reportErrorForToken(ParserErrorCode.UNEXPECTED_TOKEN, _currentToken,
|
| - [_currentToken.lexeme]);
|
| - _advance();
|
| - } else {
|
| - ClassMember member = parseClassMember(className);
|
| - if (member != null) {
|
| - members.add(member);
|
| - }
|
| - }
|
| - if (identical(_currentToken, memberStart)) {
|
| - _reportErrorForToken(ParserErrorCode.UNEXPECTED_TOKEN, _currentToken,
|
| - [_currentToken.lexeme]);
|
| - _advance();
|
| + Token skipStringLiteral(Token startToken) {
|
| + Token token = startToken;
|
| + while (token != null && _tokenMatches(token, TokenType.STRING)) {
|
| + token = token.next;
|
| + TokenType type = token.type;
|
| + if (type == TokenType.STRING_INTERPOLATION_EXPRESSION ||
|
| + type == TokenType.STRING_INTERPOLATION_IDENTIFIER) {
|
| + token = _skipStringInterpolation(token);
|
| }
|
| - memberStart = _currentToken;
|
| - type = _currentToken.type;
|
| - keyword = _currentToken.keyword;
|
| }
|
| - return members;
|
| + if (identical(token, startToken)) {
|
| + return null;
|
| + }
|
| + return token;
|
| }
|
|
|
| /**
|
| - * Parse a class type alias. The [commentAndMetadata] is the metadata to be
|
| - * associated with the member. The [abstractKeyword] is the token representing
|
| - * the 'abstract' keyword. The [classKeyword] is the token representing the
|
| - * 'class' keyword. The [className] is the name of the alias, and the
|
| - * [typeParameters] are the type parameters following the name. Return the
|
| - * class type alias that was parsed.
|
| + * Parse a list of type arguments, starting at the [startToken], without
|
| + * actually creating a type argument list or changing the current token.
|
| + * Return the token following the type argument list that was parsed, or
|
| + * `null` if the given token is not the first token in a valid type argument
|
| + * list.
|
| *
|
| - * classTypeAlias ::=
|
| - * identifier typeParameters? '=' 'abstract'? mixinApplication
|
| + * This method must be kept in sync with [parseTypeArgumentList].
|
| *
|
| - * mixinApplication ::=
|
| - * type withClause implementsClause? ';'
|
| + * typeArguments ::=
|
| + * '<' typeList '>'
|
| + *
|
| + * typeList ::=
|
| + * type (',' type)*
|
| */
|
| - ClassTypeAlias _parseClassTypeAliasAfterName(
|
| - CommentAndMetadata commentAndMetadata,
|
| - Token abstractKeyword,
|
| - Token classKeyword,
|
| - SimpleIdentifier className,
|
| - TypeParameterList typeParameters) {
|
| - Token equals = _expect(TokenType.EQ);
|
| - TypeName superclass = parseTypeName(false);
|
| - WithClause withClause = null;
|
| - if (_matchesKeyword(Keyword.WITH)) {
|
| - withClause = parseWithClause();
|
| - } else {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.EXPECTED_TOKEN, [Keyword.WITH.syntax]);
|
| + Token skipTypeArgumentList(Token startToken) {
|
| + Token token = startToken;
|
| + if (!_tokenMatches(token, TokenType.LT) &&
|
| + !_injectGenericCommentTypeList()) {
|
| + return null;
|
| }
|
| - ImplementsClause implementsClause = null;
|
| - if (_matchesKeyword(Keyword.IMPLEMENTS)) {
|
| - implementsClause = parseImplementsClause();
|
| + token = skipTypeName(token.next);
|
| + if (token == null) {
|
| + // If the start token '<' is followed by '>'
|
| + // then assume this should be type argument list but is missing a type
|
| + token = startToken.next;
|
| + if (_tokenMatches(token, TokenType.GT)) {
|
| + return token.next;
|
| + }
|
| + return null;
|
| }
|
| - Token semicolon;
|
| - if (_matches(TokenType.SEMICOLON)) {
|
| - semicolon = getAndAdvance();
|
| - } else {
|
| - if (_matches(TokenType.OPEN_CURLY_BRACKET)) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.EXPECTED_TOKEN, [TokenType.SEMICOLON.lexeme]);
|
| - Token leftBracket = getAndAdvance();
|
| - _parseClassMembers(className.name, _getEndToken(leftBracket));
|
| - _expect(TokenType.CLOSE_CURLY_BRACKET);
|
| - } else {
|
| - _reportErrorForToken(ParserErrorCode.EXPECTED_TOKEN,
|
| - _currentToken.previous, [TokenType.SEMICOLON.lexeme]);
|
| + while (_tokenMatches(token, TokenType.COMMA)) {
|
| + token = skipTypeName(token.next);
|
| + if (token == null) {
|
| + return null;
|
| }
|
| - semicolon = _createSyntheticToken(TokenType.SEMICOLON);
|
| }
|
| - return new ClassTypeAlias(
|
| - commentAndMetadata.comment,
|
| - commentAndMetadata.metadata,
|
| - classKeyword,
|
| - className,
|
| - typeParameters,
|
| - equals,
|
| - abstractKeyword,
|
| - superclass,
|
| - withClause,
|
| - implementsClause,
|
| - semicolon);
|
| + if (token.type == TokenType.GT) {
|
| + return token.next;
|
| + } else if (token.type == TokenType.GT_GT) {
|
| + Token second = new Token(TokenType.GT, token.offset + 1);
|
| + second.setNextWithoutSettingPrevious(token.next);
|
| + return second;
|
| + }
|
| + return null;
|
| }
|
|
|
| /**
|
| - * Parse all of the comment references occurring in the given array of
|
| - * documentation comments. The [tokens] are the comment tokens representing
|
| - * the documentation comments to be parsed. Return the comment references that
|
| - * were parsed.
|
| + * Parse a type name, starting at the [startToken], without actually creating
|
| + * a type name or changing the current token. Return the token following the
|
| + * type name that was parsed, or `null` if the given token is not the first
|
| + * token in a valid type name.
|
| *
|
| - * commentReference ::=
|
| - * '[' 'new'? qualified ']' libraryReference?
|
| + * This method must be kept in sync with [parseTypeName].
|
| *
|
| - * libraryReference ::=
|
| - * '(' stringLiteral ')'
|
| + * type ::=
|
| + * qualified typeArguments?
|
| */
|
| - List<CommentReference> _parseCommentReferences(
|
| - List<DocumentationCommentToken> tokens) {
|
| - List<CommentReference> references = <CommentReference>[];
|
| - bool isInGitHubCodeBlock = false;
|
| - for (DocumentationCommentToken token in tokens) {
|
| - String comment = token.lexeme;
|
| - // Skip GitHub code blocks.
|
| - // https://help.github.com/articles/creating-and-highlighting-code-blocks/
|
| - if (tokens.length != 1) {
|
| - if (comment.indexOf('```') != -1) {
|
| - isInGitHubCodeBlock = !isInGitHubCodeBlock;
|
| - }
|
| - if (isInGitHubCodeBlock) {
|
| - continue;
|
| - }
|
| - }
|
| - // Remove GitHub include code.
|
| - comment = _removeGitHubInlineCode(comment);
|
| - // Find references.
|
| - int length = comment.length;
|
| - List<List<int>> codeBlockRanges = _getCodeBlockRanges(comment);
|
| - int leftIndex = comment.indexOf('[');
|
| - while (leftIndex >= 0 && leftIndex + 1 < length) {
|
| - List<int> range = _findRange(codeBlockRanges, leftIndex);
|
| - if (range == null) {
|
| - int nameOffset = token.offset + leftIndex + 1;
|
| - int rightIndex = comment.indexOf(']', leftIndex);
|
| - if (rightIndex >= 0) {
|
| - int firstChar = comment.codeUnitAt(leftIndex + 1);
|
| - if (firstChar != 0x27 && firstChar != 0x22) {
|
| - if (_isLinkText(comment, rightIndex)) {
|
| - // TODO(brianwilkerson) Handle the case where there's a library
|
| - // URI in the link text.
|
| - } else {
|
| - CommentReference reference = parseCommentReference(
|
| - comment.substring(leftIndex + 1, rightIndex), nameOffset);
|
| - if (reference != null) {
|
| - references.add(reference);
|
| - token.references.add(reference.beginToken);
|
| - }
|
| - }
|
| - }
|
| - } else {
|
| - // terminating ']' is not typed yet
|
| - int charAfterLeft = comment.codeUnitAt(leftIndex + 1);
|
| - Token nameToken;
|
| - if (Character.isLetterOrDigit(charAfterLeft)) {
|
| - int nameEnd = StringUtilities.indexOfFirstNotLetterDigit(
|
| - comment, leftIndex + 1);
|
| - String name = comment.substring(leftIndex + 1, nameEnd);
|
| - nameToken =
|
| - new StringToken(TokenType.IDENTIFIER, name, nameOffset);
|
| - } else {
|
| - nameToken = new SyntheticStringToken(
|
| - TokenType.IDENTIFIER, '', nameOffset);
|
| - }
|
| - nameToken.setNext(new SimpleToken(TokenType.EOF, nameToken.end));
|
| - references.add(
|
| - new CommentReference(null, new SimpleIdentifier(nameToken)));
|
| - token.references.add(nameToken);
|
| - // next character
|
| - rightIndex = leftIndex + 1;
|
| - }
|
| - leftIndex = comment.indexOf('[', rightIndex);
|
| - } else {
|
| - leftIndex = comment.indexOf('[', range[1]);
|
| - }
|
| - }
|
| + Token skipTypeName(Token startToken) {
|
| + Token token = skipPrefixedIdentifier(startToken);
|
| + if (token == null) {
|
| + return null;
|
| + }
|
| + if (_tokenMatches(token, TokenType.LT)) {
|
| + token = skipTypeArgumentList(token);
|
| + }
|
| + return token;
|
| + }
|
| +
|
| + /**
|
| + * Advance to the next token in the token stream.
|
| + */
|
| + void _advance() {
|
| + _currentToken = _currentToken.next;
|
| + }
|
| +
|
| + /**
|
| + * Append the character equivalent of the given [scalarValue] to the given
|
| + * [builder]. Use the [startIndex] and [endIndex] to report an error, and
|
| + * don't append anything to the builder, if the scalar value is invalid. The
|
| + * [escapeSequence] is the escape sequence that was parsed to produce the
|
| + * scalar value (used for error reporting).
|
| + */
|
| + void _appendScalarValue(StringBuffer buffer, String escapeSequence,
|
| + int scalarValue, int startIndex, int endIndex) {
|
| + if (scalarValue < 0 ||
|
| + scalarValue > Character.MAX_CODE_POINT ||
|
| + (scalarValue >= 0xD800 && scalarValue <= 0xDFFF)) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.INVALID_CODE_POINT, [escapeSequence]);
|
| + return;
|
| + }
|
| + if (scalarValue < Character.MAX_VALUE) {
|
| + buffer.writeCharCode(scalarValue);
|
| + } else {
|
| + buffer.write(Character.toChars(scalarValue));
|
| }
|
| - return references;
|
| }
|
|
|
| /**
|
| - * Parse a list of configurations. Return the configurations that were parsed,
|
| - * or `null` if there are no configurations.
|
| + * Clone all token starting from the given [token] up to the end of the token
|
| + * stream, and return the first token in the new token stream.
|
| */
|
| - List<Configuration> _parseConfigurations() {
|
| - List<Configuration> configurations = null;
|
| - while (_matchesKeyword(Keyword.IF)) {
|
| - configurations ??= <Configuration>[];
|
| - configurations.add(parseConfiguration());
|
| + Token _cloneTokens(Token token) {
|
| + if (token == null) {
|
| + return null;
|
| }
|
| - return configurations;
|
| + token = token is CommentToken ? token.parent : token;
|
| + Token head = new Token(TokenType.EOF, -1);
|
| + head.setNext(head);
|
| + Token current = head;
|
| + while (token.type != TokenType.EOF) {
|
| + Token clone = token.copy();
|
| + current.setNext(clone);
|
| + current = clone;
|
| + token = token.next;
|
| + }
|
| + Token tail = new Token(TokenType.EOF, 0);
|
| + tail.setNext(tail);
|
| + current.setNext(tail);
|
| + return head.next;
|
| }
|
|
|
| - ConstructorDeclaration _parseConstructor(
|
| - CommentAndMetadata commentAndMetadata,
|
| - Token externalKeyword,
|
| - Token constKeyword,
|
| - Token factoryKeyword,
|
| - SimpleIdentifier returnType,
|
| - Token period,
|
| - SimpleIdentifier name,
|
| - FormalParameterList parameters) {
|
| - bool bodyAllowed = externalKeyword == null;
|
| - Token separator = null;
|
| - List<ConstructorInitializer> initializers = null;
|
| - if (_matches(TokenType.COLON)) {
|
| - separator = getAndAdvance();
|
| - initializers = <ConstructorInitializer>[];
|
| - do {
|
| - Keyword keyword = _currentToken.keyword;
|
| - if (keyword == Keyword.THIS) {
|
| - TokenType nextType = _peek().type;
|
| - if (nextType == TokenType.OPEN_PAREN) {
|
| - bodyAllowed = false;
|
| - initializers.add(_parseRedirectingConstructorInvocation(false));
|
| - } else if (nextType == TokenType.PERIOD &&
|
| - _tokenMatches(_peekAt(3), TokenType.OPEN_PAREN)) {
|
| - bodyAllowed = false;
|
| - initializers.add(_parseRedirectingConstructorInvocation(true));
|
| - } else {
|
| - initializers.add(_parseConstructorFieldInitializer(true));
|
| - }
|
| - } else if (keyword == Keyword.SUPER) {
|
| - initializers.add(parseSuperConstructorInvocation());
|
| - } else if (_matches(TokenType.OPEN_CURLY_BRACKET) ||
|
| - _matches(TokenType.FUNCTION)) {
|
| - _reportErrorForCurrentToken(ParserErrorCode.MISSING_INITIALIZER);
|
| - } else if (_enableAssertInitializer &&
|
| - _matchesKeyword(Keyword.ASSERT)) {
|
| - _parseAssertInitializer();
|
| - } else {
|
| - initializers.add(_parseConstructorFieldInitializer(false));
|
| - }
|
| - } while (_optional(TokenType.COMMA));
|
| - if (factoryKeyword != null) {
|
| - _reportErrorForToken(
|
| - ParserErrorCode.FACTORY_WITH_INITIALIZERS, factoryKeyword);
|
| + /**
|
| + * Convert the given [method] declaration into the nearest valid top-level
|
| + * function declaration (that is, the function declaration that most closely
|
| + * captures the components of the given method declaration).
|
| + */
|
| + FunctionDeclaration _convertToFunctionDeclaration(MethodDeclaration method) =>
|
| + new FunctionDeclaration(
|
| + method.documentationComment,
|
| + method.metadata,
|
| + method.externalKeyword,
|
| + method.returnType,
|
| + method.propertyKeyword,
|
| + method.name,
|
| + new FunctionExpression(
|
| + method.typeParameters, method.parameters, method.body));
|
| +
|
| + /**
|
| + * Return `true` if the current token could be the start of a compilation unit
|
| + * member. This method is used for recovery purposes to decide when to stop
|
| + * skipping tokens after finding an error while parsing a compilation unit
|
| + * member.
|
| + */
|
| + bool _couldBeStartOfCompilationUnitMember() {
|
| + Keyword keyword = _currentToken.keyword;
|
| + Token next = _currentToken.next;
|
| + TokenType nextType = next.type;
|
| + if ((keyword == Keyword.IMPORT ||
|
| + keyword == Keyword.EXPORT ||
|
| + keyword == Keyword.LIBRARY ||
|
| + keyword == Keyword.PART) &&
|
| + nextType != TokenType.PERIOD &&
|
| + nextType != TokenType.LT) {
|
| + // This looks like the start of a directive
|
| + return true;
|
| + } else if (keyword == Keyword.CLASS) {
|
| + // This looks like the start of a class definition
|
| + return true;
|
| + } else if (keyword == Keyword.TYPEDEF &&
|
| + nextType != TokenType.PERIOD &&
|
| + nextType != TokenType.LT) {
|
| + // This looks like the start of a typedef
|
| + return true;
|
| + } else if (keyword == Keyword.VOID ||
|
| + ((keyword == Keyword.GET || keyword == Keyword.SET) &&
|
| + _tokenMatchesIdentifier(next)) ||
|
| + (keyword == Keyword.OPERATOR && _isOperator(next))) {
|
| + // This looks like the start of a function
|
| + return true;
|
| + } else if (_matchesIdentifier()) {
|
| + if (nextType == TokenType.OPEN_PAREN) {
|
| + // This looks like the start of a function
|
| + return true;
|
| }
|
| - }
|
| - ConstructorName redirectedConstructor = null;
|
| - FunctionBody body;
|
| - if (_matches(TokenType.EQ)) {
|
| - separator = getAndAdvance();
|
| - redirectedConstructor = parseConstructorName();
|
| - body = new EmptyFunctionBody(_expect(TokenType.SEMICOLON));
|
| - if (factoryKeyword == null) {
|
| - _reportErrorForNode(
|
| - ParserErrorCode.REDIRECTION_IN_NON_FACTORY_CONSTRUCTOR,
|
| - redirectedConstructor);
|
| + Token token = skipReturnType(_currentToken);
|
| + if (token == null) {
|
| + return false;
|
| }
|
| - } else {
|
| - body =
|
| - parseFunctionBody(true, ParserErrorCode.MISSING_FUNCTION_BODY, false);
|
| - if (constKeyword != null &&
|
| - factoryKeyword != null &&
|
| - externalKeyword == null) {
|
| - _reportErrorForToken(ParserErrorCode.CONST_FACTORY, factoryKeyword);
|
| - } else if (body is EmptyFunctionBody) {
|
| - if (factoryKeyword != null &&
|
| - externalKeyword == null &&
|
| - _parseFunctionBodies) {
|
| - _reportErrorForToken(
|
| - ParserErrorCode.FACTORY_WITHOUT_BODY, factoryKeyword);
|
| - }
|
| - } else {
|
| - if (constKeyword != null) {
|
| - _reportErrorForNode(
|
| - ParserErrorCode.CONST_CONSTRUCTOR_WITH_BODY, body);
|
| - } else if (externalKeyword != null) {
|
| - _reportErrorForNode(
|
| - ParserErrorCode.EXTERNAL_CONSTRUCTOR_WITH_BODY, body);
|
| - } else if (!bodyAllowed) {
|
| - _reportErrorForNode(
|
| - ParserErrorCode.REDIRECTING_CONSTRUCTOR_WITH_BODY, body);
|
| - }
|
| + // TODO(brianwilkerson) This looks wrong; should we be checking 'token'?
|
| + if (keyword == Keyword.GET ||
|
| + keyword == Keyword.SET ||
|
| + (keyword == Keyword.OPERATOR && _isOperator(next)) ||
|
| + _matchesIdentifier()) {
|
| + return true;
|
| }
|
| }
|
| - return new ConstructorDeclaration(
|
| - commentAndMetadata.comment,
|
| - commentAndMetadata.metadata,
|
| - externalKeyword,
|
| - constKeyword,
|
| - factoryKeyword,
|
| - returnType,
|
| - period,
|
| - name,
|
| - parameters,
|
| - separator,
|
| - initializers,
|
| - redirectedConstructor,
|
| - body);
|
| + return false;
|
| }
|
|
|
| /**
|
| - * Parse a field initializer within a constructor. The flag [hasThis] should
|
| - * be true if the current token is `this`. Return the field initializer that
|
| - * was parsed.
|
| - *
|
| - * fieldInitializer:
|
| - * ('this' '.')? identifier '=' conditionalExpression cascadeSection*
|
| + * Return a synthetic token representing the given [keyword].
|
| */
|
| - ConstructorFieldInitializer _parseConstructorFieldInitializer(bool hasThis) {
|
| - Token keywordToken = null;
|
| - Token period = null;
|
| - if (hasThis) {
|
| - keywordToken = getAndAdvance();
|
| - period = _expect(TokenType.PERIOD);
|
| - }
|
| - SimpleIdentifier fieldName = parseSimpleIdentifier();
|
| - Token equals = null;
|
| - TokenType type = _currentToken.type;
|
| - if (type == TokenType.EQ) {
|
| - equals = getAndAdvance();
|
| - } else {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.MISSING_ASSIGNMENT_IN_INITIALIZER);
|
| - Keyword keyword = _currentToken.keyword;
|
| - if (keyword != Keyword.THIS &&
|
| - keyword != Keyword.SUPER &&
|
| - type != TokenType.OPEN_CURLY_BRACKET &&
|
| - type != TokenType.FUNCTION) {
|
| - equals = _createSyntheticToken(TokenType.EQ);
|
| - } else {
|
| - return new ConstructorFieldInitializer(keywordToken, period, fieldName,
|
| - _createSyntheticToken(TokenType.EQ), createSyntheticIdentifier());
|
| - }
|
| - }
|
| - bool wasInInitializer = _inInitializer;
|
| - _inInitializer = true;
|
| - try {
|
| - Expression expression = parseConditionalExpression();
|
| - if (_matches(TokenType.PERIOD_PERIOD)) {
|
| - List<Expression> cascadeSections = <Expression>[];
|
| - do {
|
| - Expression section = parseCascadeSection();
|
| - if (section != null) {
|
| - cascadeSections.add(section);
|
| - }
|
| - } while (_matches(TokenType.PERIOD_PERIOD));
|
| - expression = new CascadeExpression(expression, cascadeSections);
|
| + Token _createSyntheticKeyword(Keyword keyword) => _injectToken(
|
| + new Parser_SyntheticKeywordToken(keyword, _currentToken.offset));
|
| +
|
| + /**
|
| + * Return a synthetic token with the given [type].
|
| + */
|
| + Token _createSyntheticToken(TokenType type) =>
|
| + _injectToken(new StringToken(type, "", _currentToken.offset));
|
| +
|
| + /**
|
| + * Create and return a new token with the given [type]. The token will replace
|
| + * the first portion of the given [token], so it will have the same offset and
|
| + * will have any comments that might have preceeded the token.
|
| + */
|
| + Token _createToken(Token token, TokenType type, {bool isBegin: false}) {
|
| + CommentToken comments = token.precedingComments;
|
| + if (comments == null) {
|
| + if (isBegin) {
|
| + return new BeginToken(type, token.offset);
|
| }
|
| - return new ConstructorFieldInitializer(
|
| - keywordToken, period, fieldName, equals, expression);
|
| - } finally {
|
| - _inInitializer = wasInInitializer;
|
| + return new Token(type, token.offset);
|
| + } else if (isBegin) {
|
| + return new BeginTokenWithComment(type, token.offset, comments);
|
| }
|
| + return new TokenWithComment(type, token.offset, comments);
|
| }
|
|
|
| /**
|
| - * Parse a directive. The [commentAndMetadata] is the metadata to be
|
| - * associated with the directive. Return the directive that was parsed.
|
| + * Check that the given [expression] is assignable and report an error if it
|
| + * isn't.
|
| *
|
| - * directive ::=
|
| - * exportDirective
|
| - * | libraryDirective
|
| - * | importDirective
|
| - * | partDirective
|
| + * assignableExpression ::=
|
| + * primary (arguments* assignableSelector)+
|
| + * | 'super' unconditionalAssignableSelector
|
| + * | identifier
|
| + *
|
| + * unconditionalAssignableSelector ::=
|
| + * '[' expression ']'
|
| + * | '.' identifier
|
| + *
|
| + * assignableSelector ::=
|
| + * unconditionalAssignableSelector
|
| + * | '?.' identifier
|
| */
|
| - Directive _parseDirective(CommentAndMetadata commentAndMetadata) {
|
| - if (_matchesKeyword(Keyword.IMPORT)) {
|
| - return _parseImportDirective(commentAndMetadata);
|
| - } else if (_matchesKeyword(Keyword.EXPORT)) {
|
| - return _parseExportDirective(commentAndMetadata);
|
| - } else if (_matchesKeyword(Keyword.LIBRARY)) {
|
| - return _parseLibraryDirective(commentAndMetadata);
|
| - } else if (_matchesKeyword(Keyword.PART)) {
|
| - return _parsePartOrPartOfDirective(commentAndMetadata);
|
| - } else {
|
| - // Internal error: this method should not have been invoked if the current
|
| - // token was something other than one of the above.
|
| - throw new StateError(
|
| - "parseDirective invoked in an invalid state; currentToken = $_currentToken");
|
| + void _ensureAssignable(Expression expression) {
|
| + if (expression != null && !expression.isAssignable) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.ILLEGAL_ASSIGNMENT_TO_NON_ASSIGNABLE);
|
| }
|
| }
|
|
|
| /**
|
| - * Parse an enum constant declaration. Return the enum constant declaration
|
| - * that was parsed.
|
| - *
|
| - * Specified:
|
| - *
|
| - * enumConstant ::=
|
| - * id
|
| + * If the current token has the expected type, return it after advancing to
|
| + * the next token. Otherwise report an error and return the current token
|
| + * without advancing.
|
| *
|
| - * Actual:
|
| + * Note that the method [_expectGt] should be used if the argument to this
|
| + * method would be [TokenType.GT].
|
| *
|
| - * enumConstant ::=
|
| - * metadata id
|
| + * The [type] is the type of token that is expected.
|
| */
|
| - EnumConstantDeclaration _parseEnumConstantDeclaration() {
|
| - CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
|
| - SimpleIdentifier name;
|
| - if (_matchesIdentifier()) {
|
| - name = _parseSimpleIdentifierUnchecked(isDeclaration: true);
|
| - } else {
|
| - name = createSyntheticIdentifier();
|
| + Token _expect(TokenType type) {
|
| + if (_matches(type)) {
|
| + return getAndAdvance();
|
| }
|
| - if (commentAndMetadata.hasMetadata) {
|
| - _reportErrorForNode(ParserErrorCode.ANNOTATION_ON_ENUM_CONSTANT,
|
| - commentAndMetadata.metadata[0]);
|
| + // Remove uses of this method in favor of matches?
|
| + // Pass in the error code to use to report the error?
|
| + if (type == TokenType.SEMICOLON) {
|
| + if (_tokenMatches(_currentToken.next, TokenType.SEMICOLON)) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.UNEXPECTED_TOKEN, [_currentToken.lexeme]);
|
| + _advance();
|
| + return getAndAdvance();
|
| + }
|
| + _reportErrorForToken(ParserErrorCode.EXPECTED_TOKEN,
|
| + _currentToken.previous, [type.lexeme]);
|
| + return _createSyntheticToken(TokenType.SEMICOLON);
|
| }
|
| - return new EnumConstantDeclaration(
|
| - commentAndMetadata.comment, commentAndMetadata.metadata, name);
|
| + _reportErrorForCurrentToken(ParserErrorCode.EXPECTED_TOKEN, [type.lexeme]);
|
| + return _createSyntheticToken(type);
|
| }
|
|
|
| /**
|
| - * Parse an equality expression. Return the equality expression that was
|
| - * parsed.
|
| - *
|
| - * equalityExpression ::=
|
| - * relationalExpression (equalityOperator relationalExpression)?
|
| - * | 'super' equalityOperator relationalExpression
|
| + * If the current token has the type [TokenType.GT], return it after advancing
|
| + * to the next token. Otherwise report an error and create a synthetic token.
|
| */
|
| - Expression _parseEqualityExpression() {
|
| - Expression expression;
|
| - if (_currentToken.keyword == Keyword.SUPER &&
|
| - _currentToken.next.type.isEqualityOperator) {
|
| - expression = new SuperExpression(getAndAdvance());
|
| - } else {
|
| - expression = parseRelationalExpression();
|
| + Token _expectGt() {
|
| + if (_matchesGt()) {
|
| + return getAndAdvance();
|
| }
|
| - bool leftEqualityExpression = false;
|
| - while (_currentToken.type.isEqualityOperator) {
|
| - if (leftEqualityExpression) {
|
| - _reportErrorForNode(
|
| - ParserErrorCode.EQUALITY_CANNOT_BE_EQUALITY_OPERAND, expression);
|
| - }
|
| - expression = new BinaryExpression(
|
| - expression, getAndAdvance(), parseRelationalExpression());
|
| - leftEqualityExpression = true;
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.EXPECTED_TOKEN, [TokenType.GT.lexeme]);
|
| + return _createSyntheticToken(TokenType.GT);
|
| + }
|
| +
|
| + /**
|
| + * If the current token is a keyword matching the given [keyword], return it
|
| + * after advancing to the next token. Otherwise report an error and return the
|
| + * current token without advancing.
|
| + */
|
| + Token _expectKeyword(Keyword keyword) {
|
| + if (_matchesKeyword(keyword)) {
|
| + return getAndAdvance();
|
| }
|
| - return expression;
|
| + // Remove uses of this method in favor of matches?
|
| + // Pass in the error code to use to report the error?
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.EXPECTED_TOKEN, [keyword.syntax]);
|
| + return _currentToken;
|
| }
|
|
|
| /**
|
| - * Parse an export directive. The [commentAndMetadata] is the metadata to be
|
| - * associated with the directive. Return the export directive that was parsed.
|
| - *
|
| - * This method assumes that the current token matches `Keyword.EXPORT`.
|
| - *
|
| - * exportDirective ::=
|
| - * metadata 'export' stringLiteral configuration* combinator*';'
|
| + * Search the given list of [ranges] for a range that contains the given
|
| + * [index]. Return the range that was found, or `null` if none of the ranges
|
| + * contain the index.
|
| */
|
| - ExportDirective _parseExportDirective(CommentAndMetadata commentAndMetadata) {
|
| - Token exportKeyword = getAndAdvance();
|
| - StringLiteral libraryUri = _parseUri();
|
| - List<Configuration> configurations = _parseConfigurations();
|
| - List<Combinator> combinators = parseCombinators();
|
| - Token semicolon = _expect(TokenType.SEMICOLON);
|
| - return new ExportDirective(
|
| - commentAndMetadata.comment,
|
| - commentAndMetadata.metadata,
|
| - exportKeyword,
|
| - libraryUri,
|
| - configurations,
|
| - combinators,
|
| - semicolon);
|
| + List<int> _findRange(List<List<int>> ranges, int index) {
|
| + int rangeCount = ranges.length;
|
| + for (int i = 0; i < rangeCount; i++) {
|
| + List<int> range = ranges[i];
|
| + if (range[0] <= index && index <= range[1]) {
|
| + return range;
|
| + } else if (index < range[0]) {
|
| + return null;
|
| + }
|
| + }
|
| + return null;
|
| }
|
|
|
| /**
|
| - * Parse a formal parameter. At most one of `isOptional` and `isNamed` can be
|
| - * `true`. The [kind] is the kind of parameter being expected based on the
|
| - * presence or absence of group delimiters. Return the formal parameter that
|
| - * was parsed.
|
| - *
|
| - * defaultFormalParameter ::=
|
| - * normalFormalParameter ('=' expression)?
|
| - *
|
| - * defaultNamedParameter ::=
|
| - * normalFormalParameter (':' expression)?
|
| + * Return a list of the ranges of characters in the given [comment] that
|
| + * should be treated as code blocks.
|
| */
|
| - FormalParameter _parseFormalParameter(ParameterKind kind) {
|
| - NormalFormalParameter parameter = parseNormalFormalParameter();
|
| - TokenType type = _currentToken.type;
|
| - if (type == TokenType.EQ) {
|
| - Token separator = getAndAdvance();
|
| - Expression defaultValue = parseExpression2();
|
| - if (kind == ParameterKind.NAMED) {
|
| - _reportErrorForToken(
|
| - ParserErrorCode.WRONG_SEPARATOR_FOR_NAMED_PARAMETER, separator);
|
| - } else if (kind == ParameterKind.REQUIRED) {
|
| - _reportErrorForNode(
|
| - ParserErrorCode.POSITIONAL_PARAMETER_OUTSIDE_GROUP, parameter);
|
| + List<List<int>> _getCodeBlockRanges(String comment) {
|
| + List<List<int>> ranges = <List<int>>[];
|
| + int length = comment.length;
|
| + if (length < 3) {
|
| + return ranges;
|
| + }
|
| + int index = 0;
|
| + int firstChar = comment.codeUnitAt(0);
|
| + if (firstChar == 0x2F) {
|
| + int secondChar = comment.codeUnitAt(1);
|
| + int thirdChar = comment.codeUnitAt(2);
|
| + if ((secondChar == 0x2A && thirdChar == 0x2A) ||
|
| + (secondChar == 0x2F && thirdChar == 0x2F)) {
|
| + index = 3;
|
| }
|
| - return new DefaultFormalParameter(
|
| - parameter, kind, separator, defaultValue);
|
| - } else if (type == TokenType.COLON) {
|
| - Token separator = getAndAdvance();
|
| - Expression defaultValue = parseExpression2();
|
| - if (kind == ParameterKind.POSITIONAL) {
|
| - _reportErrorForToken(
|
| - ParserErrorCode.WRONG_SEPARATOR_FOR_POSITIONAL_PARAMETER,
|
| - separator);
|
| - } else if (kind == ParameterKind.REQUIRED) {
|
| - _reportErrorForNode(
|
| - ParserErrorCode.NAMED_PARAMETER_OUTSIDE_GROUP, parameter);
|
| + }
|
| + if (StringUtilities.startsWith4(comment, index, 0x20, 0x20, 0x20, 0x20)) {
|
| + int end = index + 4;
|
| + while (end < length &&
|
| + comment.codeUnitAt(end) != 0xD &&
|
| + comment.codeUnitAt(end) != 0xA) {
|
| + end = end + 1;
|
| + }
|
| + ranges.add(<int>[index, end]);
|
| + index = end;
|
| + }
|
| + while (index < length) {
|
| + int currentChar = comment.codeUnitAt(index);
|
| + if (currentChar == 0xD || currentChar == 0xA) {
|
| + index = index + 1;
|
| + while (index < length &&
|
| + Character.isWhitespace(comment.codeUnitAt(index))) {
|
| + index = index + 1;
|
| + }
|
| + if (StringUtilities.startsWith6(
|
| + comment, index, 0x2A, 0x20, 0x20, 0x20, 0x20, 0x20)) {
|
| + int end = index + 6;
|
| + while (end < length &&
|
| + comment.codeUnitAt(end) != 0xD &&
|
| + comment.codeUnitAt(end) != 0xA) {
|
| + end = end + 1;
|
| + }
|
| + ranges.add(<int>[index, end]);
|
| + index = end;
|
| + }
|
| + } else if (index + 1 < length &&
|
| + currentChar == 0x5B &&
|
| + comment.codeUnitAt(index + 1) == 0x3A) {
|
| + int end = StringUtilities.indexOf2(comment, index + 2, 0x3A, 0x5D);
|
| + if (end < 0) {
|
| + end = length;
|
| + }
|
| + ranges.add(<int>[index, end]);
|
| + index = end + 1;
|
| + } else {
|
| + index = index + 1;
|
| }
|
| - return new DefaultFormalParameter(
|
| - parameter, kind, separator, defaultValue);
|
| - } else if (kind != ParameterKind.REQUIRED) {
|
| - return new DefaultFormalParameter(parameter, kind, null, null);
|
| }
|
| - return parameter;
|
| + return ranges;
|
| }
|
|
|
| /**
|
| - * Parse a list of formal parameters given that the list starts with the given
|
| - * [leftParenthesis]. Return the formal parameters that were parsed.
|
| + * Return the end token associated with the given [beginToken], or `null` if
|
| + * either the given token is not a begin token or it does not have an end
|
| + * token associated with it.
|
| */
|
| - FormalParameterList _parseFormalParameterListAfterParen(
|
| - Token leftParenthesis) {
|
| - if (_matches(TokenType.CLOSE_PAREN)) {
|
| - return new FormalParameterList(
|
| - leftParenthesis, null, null, null, getAndAdvance());
|
| + Token _getEndToken(Token beginToken) {
|
| + if (beginToken is BeginToken) {
|
| + return beginToken.endToken;
|
| }
|
| - //
|
| - // Even though it is invalid to have default parameters outside of brackets,
|
| - // required parameters inside of brackets, or multiple groups of default and
|
| - // named parameters, we allow all of these cases so that we can recover
|
| - // better.
|
| - //
|
| - List<FormalParameter> parameters = <FormalParameter>[];
|
| - Token leftSquareBracket = null;
|
| - Token rightSquareBracket = null;
|
| - Token leftCurlyBracket = null;
|
| - Token rightCurlyBracket = null;
|
| - ParameterKind kind = ParameterKind.REQUIRED;
|
| - bool firstParameter = true;
|
| - bool reportedMultiplePositionalGroups = false;
|
| - bool reportedMultipleNamedGroups = false;
|
| - bool reportedMixedGroups = false;
|
| - bool wasOptionalParameter = false;
|
| - Token initialToken = null;
|
| - do {
|
| - if (firstParameter) {
|
| - firstParameter = false;
|
| - } else if (!_optional(TokenType.COMMA)) {
|
| - // TODO(brianwilkerson) The token is wrong, we need to recover from this
|
| - // case.
|
| - if (_getEndToken(leftParenthesis) != null) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.EXPECTED_TOKEN, [TokenType.COMMA.lexeme]);
|
| - } else {
|
| - _reportErrorForToken(ParserErrorCode.MISSING_CLOSING_PARENTHESIS,
|
| - _currentToken.previous);
|
| - break;
|
| - }
|
| - }
|
| - initialToken = _currentToken;
|
| - //
|
| - // Handle the beginning of parameter groups.
|
| - //
|
| - TokenType type = _currentToken.type;
|
| - if (type == TokenType.OPEN_SQUARE_BRACKET) {
|
| - wasOptionalParameter = true;
|
| - if (leftSquareBracket != null && !reportedMultiplePositionalGroups) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.MULTIPLE_POSITIONAL_PARAMETER_GROUPS);
|
| - reportedMultiplePositionalGroups = true;
|
| - }
|
| - if (leftCurlyBracket != null && !reportedMixedGroups) {
|
| - _reportErrorForCurrentToken(ParserErrorCode.MIXED_PARAMETER_GROUPS);
|
| - reportedMixedGroups = true;
|
| - }
|
| - leftSquareBracket = getAndAdvance();
|
| - kind = ParameterKind.POSITIONAL;
|
| - } else if (type == TokenType.OPEN_CURLY_BRACKET) {
|
| - wasOptionalParameter = true;
|
| - if (leftCurlyBracket != null && !reportedMultipleNamedGroups) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.MULTIPLE_NAMED_PARAMETER_GROUPS);
|
| - reportedMultipleNamedGroups = true;
|
| - }
|
| - if (leftSquareBracket != null && !reportedMixedGroups) {
|
| - _reportErrorForCurrentToken(ParserErrorCode.MIXED_PARAMETER_GROUPS);
|
| - reportedMixedGroups = true;
|
| - }
|
| - leftCurlyBracket = getAndAdvance();
|
| - kind = ParameterKind.NAMED;
|
| - }
|
| - //
|
| - // Parse and record the parameter.
|
| - //
|
| - FormalParameter parameter = _parseFormalParameter(kind);
|
| - parameters.add(parameter);
|
| - if (kind == ParameterKind.REQUIRED && wasOptionalParameter) {
|
| - _reportErrorForNode(
|
| - ParserErrorCode.NORMAL_BEFORE_OPTIONAL_PARAMETERS, parameter);
|
| - }
|
| - //
|
| - // Handle the end of parameter groups.
|
| - //
|
| - // TODO(brianwilkerson) Improve the detection and reporting of missing and
|
| - // mismatched delimiters.
|
| - type = _currentToken.type;
|
| + return null;
|
| + }
|
|
|
| - // Advance past trailing commas as appropriate.
|
| - if (type == TokenType.COMMA) {
|
| - // Only parse commas trailing normal (non-positional/named) params.
|
| - if (rightSquareBracket == null && rightCurlyBracket == null) {
|
| - Token next = _peek();
|
| - if (next.type == TokenType.CLOSE_PAREN ||
|
| - next.type == TokenType.CLOSE_CURLY_BRACKET ||
|
| - next.type == TokenType.CLOSE_SQUARE_BRACKET) {
|
| - _advance();
|
| - type = _currentToken.type;
|
| + bool _injectGenericComment(TokenType type, int prefixLen) {
|
| + if (parseGenericMethodComments) {
|
| + CommentToken t = _currentToken.precedingComments;
|
| + for (; t != null; t = t.next) {
|
| + if (t.type == type) {
|
| + String comment = t.lexeme.substring(prefixLen, t.lexeme.length - 2);
|
| + Token list = _scanGenericMethodComment(comment, t.offset + prefixLen);
|
| + if (list != null) {
|
| + // Remove the token from the comment stream.
|
| + t.remove();
|
| + // Insert the tokens into the stream.
|
| + _injectTokenList(list);
|
| + return true;
|
| }
|
| }
|
| }
|
| + }
|
| + return false;
|
| + }
|
|
|
| - if (type == TokenType.CLOSE_SQUARE_BRACKET) {
|
| - rightSquareBracket = getAndAdvance();
|
| - if (leftSquareBracket == null) {
|
| - if (leftCurlyBracket != null) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.WRONG_TERMINATOR_FOR_PARAMETER_GROUP, ["}"]);
|
| - rightCurlyBracket = rightSquareBracket;
|
| - rightSquareBracket = null;
|
| - } else {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.UNEXPECTED_TERMINATOR_FOR_PARAMETER_GROUP,
|
| - ["["]);
|
| - }
|
| - }
|
| - kind = ParameterKind.REQUIRED;
|
| - } else if (type == TokenType.CLOSE_CURLY_BRACKET) {
|
| - rightCurlyBracket = getAndAdvance();
|
| - if (leftCurlyBracket == null) {
|
| - if (leftSquareBracket != null) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.WRONG_TERMINATOR_FOR_PARAMETER_GROUP, ["]"]);
|
| - rightSquareBracket = rightCurlyBracket;
|
| - rightCurlyBracket = null;
|
| - } else {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.UNEXPECTED_TERMINATOR_FOR_PARAMETER_GROUP,
|
| - ["{"]);
|
| - }
|
| - }
|
| - kind = ParameterKind.REQUIRED;
|
| - }
|
| - } while (!_matches(TokenType.CLOSE_PAREN) &&
|
| - !identical(initialToken, _currentToken));
|
| - Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
|
| - //
|
| - // Check that the groups were closed correctly.
|
| - //
|
| - if (leftSquareBracket != null && rightSquareBracket == null) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.MISSING_TERMINATOR_FOR_PARAMETER_GROUP, ["]"]);
|
| + /**
|
| + * Matches a generic comment type substitution and injects it into the token
|
| + * stream. Returns true if a match was injected, otherwise false.
|
| + *
|
| + * These comments are of the form `/*=T*/`, in other words, a [TypeName]
|
| + * inside a slash-star comment, preceded by equals sign.
|
| + */
|
| + bool _injectGenericCommentTypeAssign() {
|
| + return _injectGenericComment(TokenType.GENERIC_METHOD_TYPE_ASSIGN, 3);
|
| + }
|
| +
|
| + /**
|
| + * Matches a generic comment type parameters and injects them into the token
|
| + * stream. Returns true if a match was injected, otherwise false.
|
| + *
|
| + * These comments are of the form `/*<K, V>*/`, in other words, a
|
| + * [TypeParameterList] or [TypeArgumentList] inside a slash-star comment.
|
| + */
|
| + bool _injectGenericCommentTypeList() {
|
| + return _injectGenericComment(TokenType.GENERIC_METHOD_TYPE_LIST, 2);
|
| + }
|
| +
|
| + /**
|
| + * Inject the given [token] into the token stream immediately before the
|
| + * current token.
|
| + */
|
| + Token _injectToken(Token token) {
|
| + Token previous = _currentToken.previous;
|
| + token.setNext(_currentToken);
|
| + previous.setNext(token);
|
| + return token;
|
| + }
|
| +
|
| + void _injectTokenList(Token firstToken) {
|
| + // Scanner creates a cyclic EOF token.
|
| + Token lastToken = firstToken;
|
| + while (lastToken.next.type != TokenType.EOF) {
|
| + lastToken = lastToken.next;
|
| }
|
| - if (leftCurlyBracket != null && rightCurlyBracket == null) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.MISSING_TERMINATOR_FOR_PARAMETER_GROUP, ["}"]);
|
| + // Inject these new tokens into the stream.
|
| + Token previous = _currentToken.previous;
|
| + lastToken.setNext(_currentToken);
|
| + previous.setNext(firstToken);
|
| + _currentToken = firstToken;
|
| + }
|
| +
|
| + /**
|
| + * Return `true` if the current token could be the question mark in a
|
| + * condition expression. The current token is assumed to be a question mark.
|
| + */
|
| + bool _isConditionalOperator() {
|
| + void parseOperation(Parser parser) {
|
| + parser.parseExpressionWithoutCascade();
|
| }
|
| - //
|
| - // Build the parameter list.
|
| - //
|
| - leftSquareBracket ??= leftCurlyBracket;
|
| - rightSquareBracket ??= rightCurlyBracket;
|
| - return new FormalParameterList(leftParenthesis, parameters,
|
| - leftSquareBracket, rightSquareBracket, rightParenthesis);
|
| +
|
| + Token token = _skip(_currentToken.next, parseOperation);
|
| + if (token == null || !_tokenMatches(token, TokenType.COLON)) {
|
| + return false;
|
| + }
|
| + token = _skip(token.next, parseOperation);
|
| + return token != null;
|
| }
|
|
|
| /**
|
| - * Parse a list of formal parameters. Return the formal parameters that were
|
| - * parsed.
|
| - *
|
| - * This method assumes that the current token matches `TokenType.OPEN_PAREN`.
|
| + * Return `true` if the given [character] is a valid hexadecimal digit.
|
| */
|
| - FormalParameterList _parseFormalParameterListUnchecked() {
|
| - return _parseFormalParameterListAfterParen(getAndAdvance());
|
| + bool _isHexDigit(int character) =>
|
| + (0x30 <= character && character <= 0x39) ||
|
| + (0x41 <= character && character <= 0x46) ||
|
| + (0x61 <= character && character <= 0x66);
|
| +
|
| + bool _isLikelyArgumentList() {
|
| + // Try to reduce the amount of lookahead required here before enabling
|
| + // generic methods.
|
| + if (_matches(TokenType.OPEN_PAREN)) {
|
| + return true;
|
| + }
|
| + if (!parseGenericMethods) {
|
| + return false;
|
| + }
|
| + Token token = skipTypeArgumentList(_currentToken);
|
| + return token != null && _tokenMatches(token, TokenType.OPEN_PAREN);
|
| }
|
|
|
| /**
|
| - * Parse a function declaration. The [commentAndMetadata] is the documentation
|
| - * comment and metadata to be associated with the declaration. The
|
| - * [externalKeyword] is the 'external' keyword, or `null` if the function is
|
| - * not external. The [returnType] is the return type, or `null` if there is no
|
| - * return type. The [isStatement] is `true` if the function declaration is
|
| - * being parsed as a statement. Return the function declaration that was
|
| - * parsed.
|
| + * Given that we have just found bracketed text within the given [comment],
|
| + * look to see whether that text is (a) followed by a parenthesized link
|
| + * address, (b) followed by a colon, or (c) followed by optional whitespace
|
| + * and another square bracket. The [rightIndex] is the index of the right
|
| + * bracket. Return `true` if the bracketed text is followed by a link address.
|
| *
|
| - * functionDeclaration ::=
|
| - * functionSignature functionBody
|
| - * | returnType? getOrSet identifier formalParameterList functionBody
|
| + * This method uses the syntax described by the
|
| + * <a href="http://daringfireball.net/projects/markdown/syntax">markdown</a>
|
| + * project.
|
| */
|
| - FunctionDeclaration _parseFunctionDeclaration(
|
| - CommentAndMetadata commentAndMetadata,
|
| - Token externalKeyword,
|
| - TypeName returnType) {
|
| - Token keywordToken = null;
|
| - bool isGetter = false;
|
| - Keyword keyword = _currentToken.keyword;
|
| - SimpleIdentifier name = null;
|
| - if (keyword == Keyword.GET) {
|
| - keywordToken = getAndAdvance();
|
| - isGetter = true;
|
| - } else if (keyword == Keyword.SET) {
|
| - keywordToken = getAndAdvance();
|
| + bool _isLinkText(String comment, int rightIndex) {
|
| + int length = comment.length;
|
| + int index = rightIndex + 1;
|
| + if (index >= length) {
|
| + return false;
|
| }
|
| - if (keywordToken != null && _matches(TokenType.OPEN_PAREN)) {
|
| - name = new SimpleIdentifier(keywordToken, isDeclaration: true);
|
| - keywordToken = null;
|
| - isGetter = false;
|
| - } else {
|
| - name = parseSimpleIdentifier(isDeclaration: true);
|
| + int nextChar = comment.codeUnitAt(index);
|
| + if (nextChar == 0x28 || nextChar == 0x3A) {
|
| + return true;
|
| }
|
| - TypeParameterList typeParameters = _parseGenericMethodTypeParameters();
|
| - FormalParameterList parameters = null;
|
| - if (!isGetter) {
|
| - if (_matches(TokenType.OPEN_PAREN)) {
|
| - parameters = _parseFormalParameterListUnchecked();
|
| - _validateFormalParameterList(parameters);
|
| - } else {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.MISSING_FUNCTION_PARAMETERS);
|
| - parameters = new FormalParameterList(
|
| - _createSyntheticToken(TokenType.OPEN_PAREN),
|
| - null,
|
| - null,
|
| - null,
|
| - _createSyntheticToken(TokenType.CLOSE_PAREN));
|
| + while (Character.isWhitespace(nextChar)) {
|
| + index = index + 1;
|
| + if (index >= length) {
|
| + return false;
|
| }
|
| - } else if (_matches(TokenType.OPEN_PAREN)) {
|
| - _reportErrorForCurrentToken(ParserErrorCode.GETTER_WITH_PARAMETERS);
|
| - _parseFormalParameterListUnchecked();
|
| - }
|
| - FunctionBody body;
|
| - if (externalKeyword == null) {
|
| - body = parseFunctionBody(
|
| - false, ParserErrorCode.MISSING_FUNCTION_BODY, false);
|
| - } else {
|
| - body = new EmptyFunctionBody(_expect(TokenType.SEMICOLON));
|
| + nextChar = comment.codeUnitAt(index);
|
| }
|
| -// if (!isStatement && matches(TokenType.SEMICOLON)) {
|
| -// // TODO(brianwilkerson) Improve this error message.
|
| -// reportError(ParserErrorCode.UNEXPECTED_TOKEN, currentToken.getLexeme());
|
| -// advance();
|
| -// }
|
| - return new FunctionDeclaration(
|
| - commentAndMetadata.comment,
|
| - commentAndMetadata.metadata,
|
| - externalKeyword,
|
| - returnType,
|
| - keywordToken,
|
| - name,
|
| - new FunctionExpression(typeParameters, parameters, body));
|
| + return nextChar == 0x5B;
|
| }
|
|
|
| /**
|
| - * Parse a function declaration statement. The [commentAndMetadata] is the
|
| - * documentation comment and metadata to be associated with the declaration.
|
| - * The [returnType] is the return type, or `null` if there is no return type.
|
| - * Return the function declaration statement that was parsed.
|
| - *
|
| - * functionDeclarationStatement ::=
|
| - * functionSignature functionBody
|
| + * Return `true` if the given [startToken] appears to be the beginning of an
|
| + * operator declaration.
|
| */
|
| - Statement _parseFunctionDeclarationStatementAfterReturnType(
|
| - CommentAndMetadata commentAndMetadata, TypeName returnType) {
|
| - FunctionDeclaration declaration =
|
| - _parseFunctionDeclaration(commentAndMetadata, null, returnType);
|
| - Token propertyKeyword = declaration.propertyKeyword;
|
| - if (propertyKeyword != null) {
|
| - if (propertyKeyword.keyword == Keyword.GET) {
|
| - _reportErrorForToken(
|
| - ParserErrorCode.GETTER_IN_FUNCTION, propertyKeyword);
|
| - } else {
|
| - _reportErrorForToken(
|
| - ParserErrorCode.SETTER_IN_FUNCTION, propertyKeyword);
|
| - }
|
| + bool _isOperator(Token startToken) {
|
| + // Accept any operator here, even if it is not user definable.
|
| + if (!startToken.isOperator) {
|
| + return false;
|
| }
|
| - return new FunctionDeclarationStatement(declaration);
|
| + // Token "=" means that it is actually a field initializer.
|
| + if (startToken.type == TokenType.EQ) {
|
| + return false;
|
| + }
|
| + // Consume all operator tokens.
|
| + Token token = startToken.next;
|
| + while (token.isOperator) {
|
| + token = token.next;
|
| + }
|
| + // Formal parameter list is expect now.
|
| + return _tokenMatches(token, TokenType.OPEN_PAREN);
|
| + }
|
| +
|
| + bool _isPeekGenericTypeParametersAndOpenParen() {
|
| + if (!parseGenericMethods) {
|
| + return false;
|
| + }
|
| + Token token = _skipTypeParameterList(_peek());
|
| + return token != null && _tokenMatches(token, TokenType.OPEN_PAREN);
|
| }
|
|
|
| /**
|
| - * Parse a function type alias. The [commentAndMetadata] is the metadata to be
|
| - * associated with the member. The [keyword] is the token representing the
|
| - * 'typedef' keyword. Return the function type alias that was parsed.
|
| - *
|
| - * functionTypeAlias ::=
|
| - * functionPrefix typeParameterList? formalParameterList ';'
|
| - *
|
| - * functionPrefix ::=
|
| - * returnType? name
|
| + * Return `true` if the [startToken] appears to be the first token of a type
|
| + * name that is followed by a variable or field formal parameter.
|
| */
|
| - FunctionTypeAlias _parseFunctionTypeAlias(
|
| - CommentAndMetadata commentAndMetadata, Token keyword) {
|
| - TypeName returnType = null;
|
| - if (hasReturnTypeInTypeAlias) {
|
| - returnType = parseReturnType();
|
| - }
|
| - SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
|
| - TypeParameterList typeParameters = null;
|
| - if (_matches(TokenType.LT)) {
|
| - typeParameters = parseTypeParameterList();
|
| + bool _isTypedIdentifier(Token startToken) {
|
| + Token token = skipReturnType(startToken);
|
| + if (token == null) {
|
| + return false;
|
| + } else if (_tokenMatchesIdentifier(token)) {
|
| + return true;
|
| + } else if (_tokenMatchesKeyword(token, Keyword.THIS) &&
|
| + _tokenMatches(token.next, TokenType.PERIOD) &&
|
| + _tokenMatchesIdentifier(token.next.next)) {
|
| + return true;
|
| + } else if (_tokenMatchesKeyword(startToken, Keyword.VOID)) {
|
| + // The keyword 'void' isn't a valid identifier, so it should be assumed to
|
| + // be a type name.
|
| + return true;
|
| + } else if (startToken.next != token &&
|
| + !_tokenMatches(token, TokenType.OPEN_PAREN)) {
|
| + // The type is more than a simple identifier, so it should be assumed to
|
| + // be a type name.
|
| + return true;
|
| }
|
| - TokenType type = _currentToken.type;
|
| - if (type == TokenType.SEMICOLON || type == TokenType.EOF) {
|
| - _reportErrorForCurrentToken(ParserErrorCode.MISSING_TYPEDEF_PARAMETERS);
|
| - FormalParameterList parameters = new FormalParameterList(
|
| - _createSyntheticToken(TokenType.OPEN_PAREN),
|
| - null,
|
| - null,
|
| - null,
|
| - _createSyntheticToken(TokenType.CLOSE_PAREN));
|
| - Token semicolon = _expect(TokenType.SEMICOLON);
|
| - return new FunctionTypeAlias(
|
| - commentAndMetadata.comment,
|
| - commentAndMetadata.metadata,
|
| - keyword,
|
| - returnType,
|
| - name,
|
| - typeParameters,
|
| - parameters,
|
| - semicolon);
|
| - } else if (type == TokenType.OPEN_PAREN) {
|
| - FormalParameterList parameters = _parseFormalParameterListUnchecked();
|
| - _validateFormalParameterList(parameters);
|
| - Token semicolon = _expect(TokenType.SEMICOLON);
|
| - return new FunctionTypeAlias(
|
| - commentAndMetadata.comment,
|
| - commentAndMetadata.metadata,
|
| - keyword,
|
| - returnType,
|
| - name,
|
| - typeParameters,
|
| - parameters,
|
| - semicolon);
|
| - } else {
|
| - _reportErrorForCurrentToken(ParserErrorCode.MISSING_TYPEDEF_PARAMETERS);
|
| - // Recovery: At the very least we should skip to the start of the next
|
| - // valid compilation unit member, allowing for the possibility of finding
|
| - // the typedef parameters before that point.
|
| - return new FunctionTypeAlias(
|
| - commentAndMetadata.comment,
|
| - commentAndMetadata.metadata,
|
| - keyword,
|
| - returnType,
|
| - name,
|
| - typeParameters,
|
| - new FormalParameterList(_createSyntheticToken(TokenType.OPEN_PAREN),
|
| - null, null, null, _createSyntheticToken(TokenType.CLOSE_PAREN)),
|
| - _createSyntheticToken(TokenType.SEMICOLON));
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * Increments the error reporting lock level. If level is more than `0`, then
|
| + * [reportError] wont report any error.
|
| + */
|
| + void _lockErrorListener() {
|
| + _errorListenerLock++;
|
| + }
|
| +
|
| + /**
|
| + * Return `true` if the current token has the given [type]. Note that the
|
| + * method [_matchesGt] should be used if the argument to this method would be
|
| + * [TokenType.GT].
|
| + */
|
| + bool _matches(TokenType type) => _currentToken.type == type;
|
| +
|
| + /**
|
| + * Return `true` if the current token has a type of [TokenType.GT]. Note that
|
| + * this method, unlike other variants, will modify the token stream if
|
| + * possible to match desired type. In particular, if the next token is either
|
| + * a '>>' or '>>>', the token stream will be re-written and `true` will be
|
| + * returned.
|
| + */
|
| + bool _matchesGt() {
|
| + TokenType currentType = _currentToken.type;
|
| + if (currentType == TokenType.GT) {
|
| + return true;
|
| + } else if (currentType == TokenType.GT_GT) {
|
| + Token first = _createToken(_currentToken, TokenType.GT);
|
| + Token second = new Token(TokenType.GT, _currentToken.offset + 1);
|
| + second.setNext(_currentToken.next);
|
| + first.setNext(second);
|
| + _currentToken.previous.setNext(first);
|
| + _currentToken = first;
|
| + return true;
|
| + } else if (currentType == TokenType.GT_EQ) {
|
| + Token first = _createToken(_currentToken, TokenType.GT);
|
| + Token second = new Token(TokenType.EQ, _currentToken.offset + 1);
|
| + second.setNext(_currentToken.next);
|
| + first.setNext(second);
|
| + _currentToken.previous.setNext(first);
|
| + _currentToken = first;
|
| + return true;
|
| + } else if (currentType == TokenType.GT_GT_EQ) {
|
| + int offset = _currentToken.offset;
|
| + Token first = _createToken(_currentToken, TokenType.GT);
|
| + Token second = new Token(TokenType.GT, offset + 1);
|
| + Token third = new Token(TokenType.EQ, offset + 2);
|
| + third.setNext(_currentToken.next);
|
| + second.setNext(third);
|
| + first.setNext(second);
|
| + _currentToken.previous.setNext(first);
|
| + _currentToken = first;
|
| + return true;
|
| }
|
| + return false;
|
| }
|
|
|
| /**
|
| - * Parses generic type parameters from a comment.
|
| - *
|
| - * Normally this is handled by [_parseGenericMethodTypeParameters], but if the
|
| - * code already handles the normal generic type parameters, the comment
|
| - * matcher can be called directly. For example, we may have already tried
|
| - * matching `<` (less than sign) in a method declaration, and be currently
|
| - * on the `(` (open paren) because we didn't find it. In that case, this
|
| - * function will parse the preceding comment such as `/*<T, R>*/`.
|
| + * Return `true` if the current token is a valid identifier. Valid identifiers
|
| + * include built-in identifiers (pseudo-keywords).
|
| */
|
| - TypeParameterList _parseGenericCommentTypeParameters() {
|
| - if (_injectGenericCommentTypeList()) {
|
| - return parseTypeParameterList();
|
| - }
|
| - return null;
|
| - }
|
| + bool _matchesIdentifier() => _tokenMatchesIdentifier(_currentToken);
|
|
|
| /**
|
| - * Parse the generic method or function's type parameters.
|
| - *
|
| - * For backwards compatibility this can optionally use comments.
|
| - * See [parseGenericMethodComments].
|
| + * Return `true` if the current token matches the given [keyword].
|
| */
|
| - TypeParameterList _parseGenericMethodTypeParameters() {
|
| - if (parseGenericMethods && _matches(TokenType.LT) ||
|
| - _injectGenericCommentTypeList()) {
|
| - return parseTypeParameterList();
|
| - }
|
| - return null;
|
| - }
|
| + bool _matchesKeyword(Keyword keyword) =>
|
| + _tokenMatchesKeyword(_currentToken, keyword);
|
|
|
| /**
|
| - * Parse a getter. The [commentAndMetadata] is the documentation comment and
|
| - * metadata to be associated with the declaration. The externalKeyword] is the
|
| - * 'external' token. The staticKeyword] is the static keyword, or `null` if
|
| - * the getter is not static. The [returnType] the return type that has already
|
| - * been parsed, or `null` if there was no return type. Return the getter that
|
| - * was parsed.
|
| - *
|
| - * This method assumes that the current token matches `Keyword.GET`.
|
| - *
|
| - * getter ::=
|
| - * getterSignature functionBody?
|
| - *
|
| - * getterSignature ::=
|
| - * 'external'? 'static'? returnType? 'get' identifier
|
| + * Return `true` if the current token matches the given [identifier].
|
| */
|
| - MethodDeclaration _parseGetter(CommentAndMetadata commentAndMetadata,
|
| - Token externalKeyword, Token staticKeyword, TypeName returnType) {
|
| - Token propertyKeyword = getAndAdvance();
|
| - SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
|
| - if (_matches(TokenType.OPEN_PAREN) &&
|
| - _tokenMatches(_peek(), TokenType.CLOSE_PAREN)) {
|
| - _reportErrorForCurrentToken(ParserErrorCode.GETTER_WITH_PARAMETERS);
|
| - _advance();
|
| + bool _matchesString(String identifier) =>
|
| + _currentToken.type == TokenType.IDENTIFIER &&
|
| + _currentToken.lexeme == identifier;
|
| +
|
| + /**
|
| + * If the current token has the given [type], then advance to the next token
|
| + * and return `true`. Otherwise, return `false` without advancing. This method
|
| + * should not be invoked with an argument value of [TokenType.GT].
|
| + */
|
| + bool _optional(TokenType type) {
|
| + if (_currentToken.type == type) {
|
| _advance();
|
| + return true;
|
| }
|
| - FunctionBody body = parseFunctionBody(
|
| - externalKeyword != null || staticKeyword == null,
|
| - ParserErrorCode.STATIC_GETTER_WITHOUT_BODY,
|
| - false);
|
| - if (externalKeyword != null && body is! EmptyFunctionBody) {
|
| - _reportErrorForCurrentToken(ParserErrorCode.EXTERNAL_GETTER_WITH_BODY);
|
| - }
|
| - return new MethodDeclaration(
|
| - commentAndMetadata.comment,
|
| - commentAndMetadata.metadata,
|
| - externalKeyword,
|
| - staticKeyword,
|
| - returnType,
|
| - propertyKeyword,
|
| - null,
|
| - name,
|
| - null,
|
| - null,
|
| - body);
|
| + return false;
|
| }
|
|
|
| /**
|
| - * Parse a list of identifiers. Return the list of identifiers that were
|
| - * parsed.
|
| - *
|
| - * identifierList ::=
|
| - * identifier (',' identifier)*
|
| + * Parse an argument list when we need to check for an open paren and recover
|
| + * when there isn't one. Return the argument list that was parsed.
|
| */
|
| - List<SimpleIdentifier> _parseIdentifierList() {
|
| - List<SimpleIdentifier> identifiers = <SimpleIdentifier>[
|
| - parseSimpleIdentifier()
|
| - ];
|
| - while (_optional(TokenType.COMMA)) {
|
| - identifiers.add(parseSimpleIdentifier());
|
| + ArgumentList _parseArgumentListChecked() {
|
| + if (_matches(TokenType.OPEN_PAREN)) {
|
| + return parseArgumentList();
|
| }
|
| - return identifiers;
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.EXPECTED_TOKEN, [TokenType.OPEN_PAREN.lexeme]);
|
| + // Recovery: Look to see whether there is a close paren that isn't matched
|
| + // to an open paren and if so parse the list of arguments as normal.
|
| + return new ArgumentList(_createSyntheticToken(TokenType.OPEN_PAREN), null,
|
| + _createSyntheticToken(TokenType.CLOSE_PAREN));
|
| }
|
|
|
| /**
|
| - * Parse an import directive. The [commentAndMetadata] is the metadata to be
|
| - * associated with the directive. Return the import directive that was parsed.
|
| + * Parse an assert within a constructor's initializer list. Return the assert.
|
| *
|
| - * This method assumes that the current token matches `Keyword.IMPORT`.
|
| + * This method assumes that the current token matches `Keyword.ASSERT`.
|
| *
|
| - * importDirective ::=
|
| - * metadata 'import' stringLiteral configuration* (deferred)? ('as' identifier)? combinator*';'
|
| + * assertInitializer ::=
|
| + * 'assert' '(' expression [',' expression] ')'
|
| */
|
| - ImportDirective _parseImportDirective(CommentAndMetadata commentAndMetadata) {
|
| - Token importKeyword = getAndAdvance();
|
| - StringLiteral libraryUri = _parseUri();
|
| - List<Configuration> configurations = _parseConfigurations();
|
| - Token deferredToken = null;
|
| - Token asToken = null;
|
| - SimpleIdentifier prefix = null;
|
| - if (_matchesKeyword(Keyword.DEFERRED)) {
|
| - deferredToken = getAndAdvance();
|
| - }
|
| - if (_matchesKeyword(Keyword.AS)) {
|
| - asToken = getAndAdvance();
|
| - prefix = parseSimpleIdentifier(isDeclaration: true);
|
| - } else if (deferredToken != null) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.MISSING_PREFIX_IN_DEFERRED_IMPORT);
|
| - } else if (!_matches(TokenType.SEMICOLON) &&
|
| - !_matchesString(_SHOW) &&
|
| - !_matchesString(_HIDE)) {
|
| - Token nextToken = _peek();
|
| - if (_tokenMatchesKeyword(nextToken, Keyword.AS) ||
|
| - _tokenMatchesString(nextToken, _SHOW) ||
|
| - _tokenMatchesString(nextToken, _HIDE)) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.UNEXPECTED_TOKEN, [_currentToken]);
|
| - _advance();
|
| - if (_matchesKeyword(Keyword.AS)) {
|
| - asToken = getAndAdvance();
|
| - prefix = parseSimpleIdentifier(isDeclaration: true);
|
| - }
|
| - }
|
| + void _parseAssertInitializer() {
|
| + // TODO(brianwilkerson) Capture the syntax in the AST using a new class,
|
| + // such as AssertInitializer
|
| + Token keyword = getAndAdvance();
|
| + Token leftParen = _expect(TokenType.OPEN_PAREN);
|
| + Expression expression = parseExpression2();
|
| + Token comma;
|
| + Expression message;
|
| + if (_matches(TokenType.COMMA)) {
|
| + comma = getAndAdvance();
|
| + message = parseExpression2();
|
| }
|
| - List<Combinator> combinators = parseCombinators();
|
| - Token semicolon = _expect(TokenType.SEMICOLON);
|
| - return new ImportDirective(
|
| - commentAndMetadata.comment,
|
| - commentAndMetadata.metadata,
|
| - importKeyword,
|
| - libraryUri,
|
| - configurations,
|
| - deferredToken,
|
| - asToken,
|
| - prefix,
|
| - combinators,
|
| - semicolon);
|
| + Token rightParen = _expect(TokenType.CLOSE_PAREN);
|
| +// return new AssertInitializer(
|
| +// keyword, leftParen, expression, comma, message, rightParen);
|
| }
|
|
|
| /**
|
| - * Parse a list of initialized identifiers. The [commentAndMetadata] is the
|
| - * documentation comment and metadata to be associated with the declaration.
|
| - * The [staticKeyword] is the static keyword, or `null` if the getter is not
|
| - * static. The [keyword] is the token representing the 'final', 'const' or
|
| - * 'var' keyword, or `null` if there is no keyword. The [type] is the type
|
| - * that has already been parsed, or `null` if 'var' was provided. Return the
|
| - * getter that was parsed.
|
| - *
|
| - * ?? ::=
|
| - * 'static'? ('var' | type) initializedIdentifierList ';'
|
| - * | 'final' type? initializedIdentifierList ';'
|
| - *
|
| - * initializedIdentifierList ::=
|
| - * initializedIdentifier (',' initializedIdentifier)*
|
| - *
|
| - * initializedIdentifier ::=
|
| - * identifier ('=' expression)?
|
| + * Parse an assignable expression given that the current token is not 'super'.
|
| + * The [primaryAllowed] is `true` if the expression is allowed to be a primary
|
| + * without any assignable selector. Return the assignable expression that was
|
| + * parsed.
|
| */
|
| - FieldDeclaration _parseInitializedIdentifierList(
|
| - CommentAndMetadata commentAndMetadata,
|
| - Token staticKeyword,
|
| - Token keyword,
|
| - TypeName type) {
|
| - VariableDeclarationList fieldList =
|
| - parseVariableDeclarationListAfterType(null, keyword, type);
|
| - return new FieldDeclaration(
|
| - commentAndMetadata.comment,
|
| - commentAndMetadata.metadata,
|
| - staticKeyword,
|
| - fieldList,
|
| - _expect(TokenType.SEMICOLON));
|
| + Expression _parseAssignableExpressionNotStartingWithSuper(
|
| + bool primaryAllowed) {
|
| + //
|
| + // A primary expression can start with an identifier. We resolve the
|
| + // ambiguity by determining whether the primary consists of anything other
|
| + // than an identifier and/or is followed by an assignableSelector.
|
| + //
|
| + Expression expression = parsePrimaryExpression();
|
| + bool isOptional = primaryAllowed || expression is SimpleIdentifier;
|
| + while (true) {
|
| + while (_isLikelyArgumentList()) {
|
| + TypeArgumentList typeArguments = _parseOptionalTypeArguments();
|
| + ArgumentList argumentList = parseArgumentList();
|
| + Expression currentExpression = expression;
|
| + if (currentExpression is SimpleIdentifier) {
|
| + expression = new MethodInvocation(
|
| + null, null, currentExpression, typeArguments, argumentList);
|
| + } else if (currentExpression is PrefixedIdentifier) {
|
| + expression = new MethodInvocation(
|
| + currentExpression.prefix,
|
| + currentExpression.period,
|
| + currentExpression.identifier,
|
| + typeArguments,
|
| + argumentList);
|
| + } else if (currentExpression is PropertyAccess) {
|
| + expression = new MethodInvocation(
|
| + currentExpression.target,
|
| + currentExpression.operator,
|
| + currentExpression.propertyName,
|
| + typeArguments,
|
| + argumentList);
|
| + } else {
|
| + expression = new FunctionExpressionInvocation(
|
| + expression, typeArguments, argumentList);
|
| + }
|
| + if (!primaryAllowed) {
|
| + isOptional = false;
|
| + }
|
| + }
|
| + Expression selectorExpression = parseAssignableSelector(
|
| + expression, isOptional || (expression is PrefixedIdentifier));
|
| + if (identical(selectorExpression, expression)) {
|
| + if (!isOptional && (expression is PrefixedIdentifier)) {
|
| + PrefixedIdentifier identifier = expression as PrefixedIdentifier;
|
| + expression = new PropertyAccess(
|
| + identifier.prefix, identifier.period, identifier.identifier);
|
| + }
|
| + return expression;
|
| + }
|
| + expression = selectorExpression;
|
| + isOptional = true;
|
| + }
|
| }
|
|
|
| /**
|
| - * Parse an instance creation expression. The [keyword] is the 'new' or
|
| - * 'const' keyword that introduces the expression. Return the instance
|
| - * creation expression that was parsed.
|
| + * Parse a block when we need to check for an open curly brace and recover
|
| + * when there isn't one. Return the block that was parsed.
|
| *
|
| - * instanceCreationExpression ::=
|
| - * ('new' | 'const') type ('.' identifier)? argumentList
|
| + * block ::=
|
| + * '{' statements '}'
|
| */
|
| - InstanceCreationExpression _parseInstanceCreationExpression(Token keyword) {
|
| - ConstructorName constructorName = parseConstructorName();
|
| - ArgumentList argumentList = _parseArgumentListChecked();
|
| - return new InstanceCreationExpression(
|
| - keyword, constructorName, argumentList);
|
| + Block _parseBlockChecked() {
|
| + if (_matches(TokenType.OPEN_CURLY_BRACKET)) {
|
| + return parseBlock();
|
| + }
|
| + // TODO(brianwilkerson) Improve the error message.
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.EXPECTED_TOKEN, [TokenType.OPEN_CURLY_BRACKET.lexeme]);
|
| + // Recovery: Check for an unmatched closing curly bracket and parse
|
| + // statements until it is reached.
|
| + return new Block(_createSyntheticToken(TokenType.OPEN_CURLY_BRACKET), null,
|
| + _createSyntheticToken(TokenType.CLOSE_CURLY_BRACKET));
|
| }
|
|
|
| /**
|
| - * Parse a library directive. The [commentAndMetadata] is the metadata to be
|
| - * associated with the directive. Return the library directive that was
|
| - * parsed.
|
| - *
|
| - * This method assumes that the current token matches `Keyword.LIBRARY`.
|
| + * Parse a list of class members. The [className] is the name of the class
|
| + * whose members are being parsed. The [closingBracket] is the closing bracket
|
| + * for the class, or `null` if the closing bracket is missing. Return the list
|
| + * of class members that were parsed.
|
| *
|
| - * libraryDirective ::=
|
| - * metadata 'library' identifier ';'
|
| + * classMembers ::=
|
| + * (metadata memberDefinition)*
|
| */
|
| - LibraryDirective _parseLibraryDirective(
|
| - CommentAndMetadata commentAndMetadata) {
|
| - Token keyword = getAndAdvance();
|
| - LibraryIdentifier libraryName = _parseLibraryName(
|
| - ParserErrorCode.MISSING_NAME_IN_LIBRARY_DIRECTIVE, keyword);
|
| - Token semicolon = _expect(TokenType.SEMICOLON);
|
| - return new LibraryDirective(commentAndMetadata.comment,
|
| - commentAndMetadata.metadata, keyword, libraryName, semicolon);
|
| + List<ClassMember> _parseClassMembers(String className, Token closingBracket) {
|
| + List<ClassMember> members = <ClassMember>[];
|
| + Token memberStart = _currentToken;
|
| + TokenType type = _currentToken.type;
|
| + Keyword keyword = _currentToken.keyword;
|
| + while (type != TokenType.EOF &&
|
| + type != TokenType.CLOSE_CURLY_BRACKET &&
|
| + (closingBracket != null ||
|
| + (keyword != Keyword.CLASS && keyword != Keyword.TYPEDEF))) {
|
| + if (type == TokenType.SEMICOLON) {
|
| + _reportErrorForToken(ParserErrorCode.UNEXPECTED_TOKEN, _currentToken,
|
| + [_currentToken.lexeme]);
|
| + _advance();
|
| + } else {
|
| + ClassMember member = parseClassMember(className);
|
| + if (member != null) {
|
| + members.add(member);
|
| + }
|
| + }
|
| + if (identical(_currentToken, memberStart)) {
|
| + _reportErrorForToken(ParserErrorCode.UNEXPECTED_TOKEN, _currentToken,
|
| + [_currentToken.lexeme]);
|
| + _advance();
|
| + }
|
| + memberStart = _currentToken;
|
| + type = _currentToken.type;
|
| + keyword = _currentToken.keyword;
|
| + }
|
| + return members;
|
| }
|
|
|
| /**
|
| - * Parse a library name. The [missingNameError] is the error code to be used
|
| - * if the library name is missing. The [missingNameToken] is the token
|
| - * associated with the error produced if the library name is missing. Return
|
| - * the library name that was parsed.
|
| + * Parse a class type alias. The [commentAndMetadata] is the metadata to be
|
| + * associated with the member. The [abstractKeyword] is the token representing
|
| + * the 'abstract' keyword. The [classKeyword] is the token representing the
|
| + * 'class' keyword. The [className] is the name of the alias, and the
|
| + * [typeParameters] are the type parameters following the name. Return the
|
| + * class type alias that was parsed.
|
| *
|
| - * libraryName ::=
|
| - * libraryIdentifier
|
| + * classTypeAlias ::=
|
| + * identifier typeParameters? '=' 'abstract'? mixinApplication
|
| + *
|
| + * mixinApplication ::=
|
| + * type withClause implementsClause? ';'
|
| */
|
| - LibraryIdentifier _parseLibraryName(
|
| - ParserErrorCode missingNameError, Token missingNameToken) {
|
| - if (_matchesIdentifier()) {
|
| - return parseLibraryIdentifier();
|
| - } else if (_matches(TokenType.STRING)) {
|
| - // Recovery: This should be extended to handle arbitrary tokens until we
|
| - // can find a token that can start a compilation unit member.
|
| - StringLiteral string = parseStringLiteral();
|
| - _reportErrorForNode(ParserErrorCode.NON_IDENTIFIER_LIBRARY_NAME, string);
|
| + ClassTypeAlias _parseClassTypeAliasAfterName(
|
| + CommentAndMetadata commentAndMetadata,
|
| + Token abstractKeyword,
|
| + Token classKeyword,
|
| + SimpleIdentifier className,
|
| + TypeParameterList typeParameters) {
|
| + Token equals = _expect(TokenType.EQ);
|
| + TypeName superclass = parseTypeName(false);
|
| + WithClause withClause = null;
|
| + if (_matchesKeyword(Keyword.WITH)) {
|
| + withClause = parseWithClause();
|
| } else {
|
| - _reportErrorForToken(missingNameError, missingNameToken);
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.EXPECTED_TOKEN, [Keyword.WITH.syntax]);
|
| }
|
| - return new LibraryIdentifier(
|
| - <SimpleIdentifier>[createSyntheticIdentifier()]);
|
| + ImplementsClause implementsClause = null;
|
| + if (_matchesKeyword(Keyword.IMPLEMENTS)) {
|
| + implementsClause = parseImplementsClause();
|
| + }
|
| + Token semicolon;
|
| + if (_matches(TokenType.SEMICOLON)) {
|
| + semicolon = getAndAdvance();
|
| + } else {
|
| + if (_matches(TokenType.OPEN_CURLY_BRACKET)) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.EXPECTED_TOKEN, [TokenType.SEMICOLON.lexeme]);
|
| + Token leftBracket = getAndAdvance();
|
| + _parseClassMembers(className.name, _getEndToken(leftBracket));
|
| + _expect(TokenType.CLOSE_CURLY_BRACKET);
|
| + } else {
|
| + _reportErrorForToken(ParserErrorCode.EXPECTED_TOKEN,
|
| + _currentToken.previous, [TokenType.SEMICOLON.lexeme]);
|
| + }
|
| + semicolon = _createSyntheticToken(TokenType.SEMICOLON);
|
| + }
|
| + return new ClassTypeAlias(
|
| + commentAndMetadata.comment,
|
| + commentAndMetadata.metadata,
|
| + classKeyword,
|
| + className,
|
| + typeParameters,
|
| + equals,
|
| + abstractKeyword,
|
| + superclass,
|
| + withClause,
|
| + implementsClause,
|
| + semicolon);
|
| }
|
|
|
| /**
|
| - * Parse a list literal. The [modifier] is the 'const' modifier appearing
|
| - * before the literal, or `null` if there is no modifier. The [typeArguments]
|
| - * is the type arguments appearing before the literal, or `null` if there are
|
| - * no type arguments. Return the list literal that was parsed.
|
| - *
|
| - * This method assumes that the current token matches either
|
| - * `TokenType.OPEN_SQUARE_BRACKET` or `TokenType.INDEX`.
|
| - *
|
| - * listLiteral ::=
|
| - * 'const'? typeArguments? '[' (expressionList ','?)? ']'
|
| + * Parse a list of configurations. Return the configurations that were parsed,
|
| + * or `null` if there are no configurations.
|
| */
|
| - ListLiteral _parseListLiteral(
|
| - Token modifier, TypeArgumentList typeArguments) {
|
| - if (_matches(TokenType.INDEX)) {
|
| - // Split the token into two separate tokens.
|
| - BeginToken leftBracket = _createToken(
|
| - _currentToken, TokenType.OPEN_SQUARE_BRACKET,
|
| - isBegin: true);
|
| - Token rightBracket =
|
| - new Token(TokenType.CLOSE_SQUARE_BRACKET, _currentToken.offset + 1);
|
| - leftBracket.endToken = rightBracket;
|
| - rightBracket.setNext(_currentToken.next);
|
| - leftBracket.setNext(rightBracket);
|
| - _currentToken.previous.setNext(leftBracket);
|
| - _currentToken = _currentToken.next;
|
| - return new ListLiteral(
|
| - modifier, typeArguments, leftBracket, null, rightBracket);
|
| - }
|
| - Token leftBracket = getAndAdvance();
|
| - if (_matches(TokenType.CLOSE_SQUARE_BRACKET)) {
|
| - return new ListLiteral(
|
| - modifier, typeArguments, leftBracket, null, getAndAdvance());
|
| + List<Configuration> _parseConfigurations() {
|
| + List<Configuration> configurations = null;
|
| + while (_matchesKeyword(Keyword.IF)) {
|
| + configurations ??= <Configuration>[];
|
| + configurations.add(parseConfiguration());
|
| }
|
| - bool wasInInitializer = _inInitializer;
|
| - _inInitializer = false;
|
| - try {
|
| - List<Expression> elements = <Expression>[parseExpression2()];
|
| - while (_optional(TokenType.COMMA)) {
|
| - if (_matches(TokenType.CLOSE_SQUARE_BRACKET)) {
|
| - return new ListLiteral(
|
| - modifier, typeArguments, leftBracket, elements, getAndAdvance());
|
| + return configurations;
|
| + }
|
| +
|
| + ConstructorDeclaration _parseConstructor(
|
| + CommentAndMetadata commentAndMetadata,
|
| + Token externalKeyword,
|
| + Token constKeyword,
|
| + Token factoryKeyword,
|
| + SimpleIdentifier returnType,
|
| + Token period,
|
| + SimpleIdentifier name,
|
| + FormalParameterList parameters) {
|
| + bool bodyAllowed = externalKeyword == null;
|
| + Token separator = null;
|
| + List<ConstructorInitializer> initializers = null;
|
| + if (_matches(TokenType.COLON)) {
|
| + separator = getAndAdvance();
|
| + initializers = <ConstructorInitializer>[];
|
| + do {
|
| + Keyword keyword = _currentToken.keyword;
|
| + if (keyword == Keyword.THIS) {
|
| + TokenType nextType = _peek().type;
|
| + if (nextType == TokenType.OPEN_PAREN) {
|
| + bodyAllowed = false;
|
| + initializers.add(parseRedirectingConstructorInvocation(false));
|
| + } else if (nextType == TokenType.PERIOD &&
|
| + _tokenMatches(_peekAt(3), TokenType.OPEN_PAREN)) {
|
| + bodyAllowed = false;
|
| + initializers.add(parseRedirectingConstructorInvocation(true));
|
| + } else {
|
| + initializers.add(parseConstructorFieldInitializer(true));
|
| + }
|
| + } else if (keyword == Keyword.SUPER) {
|
| + initializers.add(parseSuperConstructorInvocation());
|
| + } else if (_matches(TokenType.OPEN_CURLY_BRACKET) ||
|
| + _matches(TokenType.FUNCTION)) {
|
| + _reportErrorForCurrentToken(ParserErrorCode.MISSING_INITIALIZER);
|
| + } else if (_enableAssertInitializer &&
|
| + _matchesKeyword(Keyword.ASSERT)) {
|
| + _parseAssertInitializer();
|
| + } else {
|
| + initializers.add(parseConstructorFieldInitializer(false));
|
| + }
|
| + } while (_optional(TokenType.COMMA));
|
| + if (factoryKeyword != null) {
|
| + _reportErrorForToken(
|
| + ParserErrorCode.FACTORY_WITH_INITIALIZERS, factoryKeyword);
|
| + }
|
| + }
|
| + ConstructorName redirectedConstructor = null;
|
| + FunctionBody body;
|
| + if (_matches(TokenType.EQ)) {
|
| + separator = getAndAdvance();
|
| + redirectedConstructor = parseConstructorName();
|
| + body = new EmptyFunctionBody(_expect(TokenType.SEMICOLON));
|
| + if (factoryKeyword == null) {
|
| + _reportErrorForNode(
|
| + ParserErrorCode.REDIRECTION_IN_NON_FACTORY_CONSTRUCTOR,
|
| + redirectedConstructor);
|
| + }
|
| + } else {
|
| + body =
|
| + parseFunctionBody(true, ParserErrorCode.MISSING_FUNCTION_BODY, false);
|
| + if (constKeyword != null &&
|
| + factoryKeyword != null &&
|
| + externalKeyword == null) {
|
| + _reportErrorForToken(ParserErrorCode.CONST_FACTORY, factoryKeyword);
|
| + } else if (body is EmptyFunctionBody) {
|
| + if (factoryKeyword != null &&
|
| + externalKeyword == null &&
|
| + _parseFunctionBodies) {
|
| + _reportErrorForToken(
|
| + ParserErrorCode.FACTORY_WITHOUT_BODY, factoryKeyword);
|
| + }
|
| + } else {
|
| + if (constKeyword != null) {
|
| + _reportErrorForNode(
|
| + ParserErrorCode.CONST_CONSTRUCTOR_WITH_BODY, body);
|
| + } else if (externalKeyword != null) {
|
| + _reportErrorForNode(
|
| + ParserErrorCode.EXTERNAL_CONSTRUCTOR_WITH_BODY, body);
|
| + } else if (!bodyAllowed) {
|
| + _reportErrorForNode(
|
| + ParserErrorCode.REDIRECTING_CONSTRUCTOR_WITH_BODY, body);
|
| }
|
| - elements.add(parseExpression2());
|
| }
|
| - Token rightBracket = _expect(TokenType.CLOSE_SQUARE_BRACKET);
|
| - return new ListLiteral(
|
| - modifier, typeArguments, leftBracket, elements, rightBracket);
|
| - } finally {
|
| - _inInitializer = wasInInitializer;
|
| }
|
| + return new ConstructorDeclaration(
|
| + commentAndMetadata.comment,
|
| + commentAndMetadata.metadata,
|
| + externalKeyword,
|
| + constKeyword,
|
| + factoryKeyword,
|
| + returnType,
|
| + period,
|
| + name,
|
| + parameters,
|
| + separator,
|
| + initializers,
|
| + redirectedConstructor,
|
| + body);
|
| }
|
|
|
| /**
|
| - * Parse a logical and expression. Return the logical and expression that was
|
| - * parsed.
|
| + * Parse an enum constant declaration. Return the enum constant declaration
|
| + * that was parsed.
|
| *
|
| - * logicalAndExpression ::=
|
| - * equalityExpression ('&&' equalityExpression)*
|
| + * Specified:
|
| + *
|
| + * enumConstant ::=
|
| + * id
|
| + *
|
| + * Actual:
|
| + *
|
| + * enumConstant ::=
|
| + * metadata id
|
| */
|
| - Expression _parseLogicalAndExpression() {
|
| - Expression expression = _parseEqualityExpression();
|
| - while (_currentToken.type == TokenType.AMPERSAND_AMPERSAND) {
|
| - expression = new BinaryExpression(
|
| - expression, getAndAdvance(), _parseEqualityExpression());
|
| + EnumConstantDeclaration _parseEnumConstantDeclaration() {
|
| + CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
|
| + SimpleIdentifier name;
|
| + if (_matchesIdentifier()) {
|
| + name = _parseSimpleIdentifierUnchecked(isDeclaration: true);
|
| + } else {
|
| + name = createSyntheticIdentifier();
|
| }
|
| - return expression;
|
| + if (commentAndMetadata.hasMetadata) {
|
| + _reportErrorForNode(ParserErrorCode.ANNOTATION_ON_ENUM_CONSTANT,
|
| + commentAndMetadata.metadata[0]);
|
| + }
|
| + return new EnumConstantDeclaration(
|
| + commentAndMetadata.comment, commentAndMetadata.metadata, name);
|
| }
|
|
|
| /**
|
| - * Parse a map literal. The [modifier] is the 'const' modifier appearing
|
| - * before the literal, or `null` if there is no modifier. The [typeArguments]
|
| - * is the type arguments that were declared, or `null` if there are no type
|
| - * arguments. Return the map literal that was parsed.
|
| - *
|
| - * This method assumes that the current token matches
|
| - * `TokenType.OPEN_CURLY_BRACKET`.
|
| - *
|
| - * mapLiteral ::=
|
| - * 'const'? typeArguments? '{' (mapLiteralEntry (',' mapLiteralEntry)* ','?)? '}'
|
| + * Parse a list of formal parameters given that the list starts with the given
|
| + * [leftParenthesis]. Return the formal parameters that were parsed.
|
| */
|
| - MapLiteral _parseMapLiteral(Token modifier, TypeArgumentList typeArguments) {
|
| - Token leftBracket = getAndAdvance();
|
| - if (_matches(TokenType.CLOSE_CURLY_BRACKET)) {
|
| - return new MapLiteral(
|
| - modifier, typeArguments, leftBracket, null, getAndAdvance());
|
| + FormalParameterList _parseFormalParameterListAfterParen(
|
| + Token leftParenthesis) {
|
| + if (_matches(TokenType.CLOSE_PAREN)) {
|
| + return new FormalParameterList(
|
| + leftParenthesis, null, null, null, getAndAdvance());
|
| }
|
| - bool wasInInitializer = _inInitializer;
|
| - _inInitializer = false;
|
| - try {
|
| - List<MapLiteralEntry> entries = <MapLiteralEntry>[parseMapLiteralEntry()];
|
| - while (_optional(TokenType.COMMA)) {
|
| - if (_matches(TokenType.CLOSE_CURLY_BRACKET)) {
|
| - return new MapLiteral(
|
| - modifier, typeArguments, leftBracket, entries, getAndAdvance());
|
| + //
|
| + // Even though it is invalid to have default parameters outside of brackets,
|
| + // required parameters inside of brackets, or multiple groups of default and
|
| + // named parameters, we allow all of these cases so that we can recover
|
| + // better.
|
| + //
|
| + List<FormalParameter> parameters = <FormalParameter>[];
|
| + Token leftSquareBracket = null;
|
| + Token rightSquareBracket = null;
|
| + Token leftCurlyBracket = null;
|
| + Token rightCurlyBracket = null;
|
| + ParameterKind kind = ParameterKind.REQUIRED;
|
| + bool firstParameter = true;
|
| + bool reportedMultiplePositionalGroups = false;
|
| + bool reportedMultipleNamedGroups = false;
|
| + bool reportedMixedGroups = false;
|
| + bool wasOptionalParameter = false;
|
| + Token initialToken = null;
|
| + do {
|
| + if (firstParameter) {
|
| + firstParameter = false;
|
| + } else if (!_optional(TokenType.COMMA)) {
|
| + // TODO(brianwilkerson) The token is wrong, we need to recover from this
|
| + // case.
|
| + if (_getEndToken(leftParenthesis) != null) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.EXPECTED_TOKEN, [TokenType.COMMA.lexeme]);
|
| + } else {
|
| + _reportErrorForToken(ParserErrorCode.MISSING_CLOSING_PARENTHESIS,
|
| + _currentToken.previous);
|
| + break;
|
| }
|
| - entries.add(parseMapLiteralEntry());
|
| }
|
| - Token rightBracket = _expect(TokenType.CLOSE_CURLY_BRACKET);
|
| - return new MapLiteral(
|
| - modifier, typeArguments, leftBracket, entries, rightBracket);
|
| - } finally {
|
| - _inInitializer = wasInInitializer;
|
| + initialToken = _currentToken;
|
| + //
|
| + // Handle the beginning of parameter groups.
|
| + //
|
| + TokenType type = _currentToken.type;
|
| + if (type == TokenType.OPEN_SQUARE_BRACKET) {
|
| + wasOptionalParameter = true;
|
| + if (leftSquareBracket != null && !reportedMultiplePositionalGroups) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.MULTIPLE_POSITIONAL_PARAMETER_GROUPS);
|
| + reportedMultiplePositionalGroups = true;
|
| + }
|
| + if (leftCurlyBracket != null && !reportedMixedGroups) {
|
| + _reportErrorForCurrentToken(ParserErrorCode.MIXED_PARAMETER_GROUPS);
|
| + reportedMixedGroups = true;
|
| + }
|
| + leftSquareBracket = getAndAdvance();
|
| + kind = ParameterKind.POSITIONAL;
|
| + } else if (type == TokenType.OPEN_CURLY_BRACKET) {
|
| + wasOptionalParameter = true;
|
| + if (leftCurlyBracket != null && !reportedMultipleNamedGroups) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.MULTIPLE_NAMED_PARAMETER_GROUPS);
|
| + reportedMultipleNamedGroups = true;
|
| + }
|
| + if (leftSquareBracket != null && !reportedMixedGroups) {
|
| + _reportErrorForCurrentToken(ParserErrorCode.MIXED_PARAMETER_GROUPS);
|
| + reportedMixedGroups = true;
|
| + }
|
| + leftCurlyBracket = getAndAdvance();
|
| + kind = ParameterKind.NAMED;
|
| + }
|
| + //
|
| + // Parse and record the parameter.
|
| + //
|
| + FormalParameter parameter = parseFormalParameter(kind);
|
| + parameters.add(parameter);
|
| + if (kind == ParameterKind.REQUIRED && wasOptionalParameter) {
|
| + _reportErrorForNode(
|
| + ParserErrorCode.NORMAL_BEFORE_OPTIONAL_PARAMETERS, parameter);
|
| + }
|
| + //
|
| + // Handle the end of parameter groups.
|
| + //
|
| + // TODO(brianwilkerson) Improve the detection and reporting of missing and
|
| + // mismatched delimiters.
|
| + type = _currentToken.type;
|
| +
|
| + // Advance past trailing commas as appropriate.
|
| + if (type == TokenType.COMMA) {
|
| + // Only parse commas trailing normal (non-positional/named) params.
|
| + if (rightSquareBracket == null && rightCurlyBracket == null) {
|
| + Token next = _peek();
|
| + if (next.type == TokenType.CLOSE_PAREN ||
|
| + next.type == TokenType.CLOSE_CURLY_BRACKET ||
|
| + next.type == TokenType.CLOSE_SQUARE_BRACKET) {
|
| + _advance();
|
| + type = _currentToken.type;
|
| + }
|
| + }
|
| + }
|
| +
|
| + if (type == TokenType.CLOSE_SQUARE_BRACKET) {
|
| + rightSquareBracket = getAndAdvance();
|
| + if (leftSquareBracket == null) {
|
| + if (leftCurlyBracket != null) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.WRONG_TERMINATOR_FOR_PARAMETER_GROUP, ["}"]);
|
| + rightCurlyBracket = rightSquareBracket;
|
| + rightSquareBracket = null;
|
| + } else {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.UNEXPECTED_TERMINATOR_FOR_PARAMETER_GROUP,
|
| + ["["]);
|
| + }
|
| + }
|
| + kind = ParameterKind.REQUIRED;
|
| + } else if (type == TokenType.CLOSE_CURLY_BRACKET) {
|
| + rightCurlyBracket = getAndAdvance();
|
| + if (leftCurlyBracket == null) {
|
| + if (leftSquareBracket != null) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.WRONG_TERMINATOR_FOR_PARAMETER_GROUP, ["]"]);
|
| + rightSquareBracket = rightCurlyBracket;
|
| + rightCurlyBracket = null;
|
| + } else {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.UNEXPECTED_TERMINATOR_FOR_PARAMETER_GROUP,
|
| + ["{"]);
|
| + }
|
| + }
|
| + kind = ParameterKind.REQUIRED;
|
| + }
|
| + } while (!_matches(TokenType.CLOSE_PAREN) &&
|
| + !identical(initialToken, _currentToken));
|
| + Token rightParenthesis = _expect(TokenType.CLOSE_PAREN);
|
| + //
|
| + // Check that the groups were closed correctly.
|
| + //
|
| + if (leftSquareBracket != null && rightSquareBracket == null) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.MISSING_TERMINATOR_FOR_PARAMETER_GROUP, ["]"]);
|
| + }
|
| + if (leftCurlyBracket != null && rightCurlyBracket == null) {
|
| + _reportErrorForCurrentToken(
|
| + ParserErrorCode.MISSING_TERMINATOR_FOR_PARAMETER_GROUP, ["}"]);
|
| }
|
| + //
|
| + // Build the parameter list.
|
| + //
|
| + leftSquareBracket ??= leftCurlyBracket;
|
| + rightSquareBracket ??= rightCurlyBracket;
|
| + return new FormalParameterList(leftParenthesis, parameters,
|
| + leftSquareBracket, rightSquareBracket, rightParenthesis);
|
| }
|
|
|
| /**
|
| - * Parse a method declaration. The [commentAndMetadata] is the documentation
|
| - * comment and metadata to be associated with the declaration. The
|
| - * [externalKeyword] is the 'external' token. The [staticKeyword] is the
|
| - * static keyword, or `null` if the getter is not static. The [returnType] is
|
| - * the return type of the method. The [name] is the name of the method. The
|
| - * [parameters] is the parameters to the method. Return the method declaration
|
| - * that was parsed.
|
| + * Parse a list of formal parameters. Return the formal parameters that were
|
| + * parsed.
|
| *
|
| - * functionDeclaration ::=
|
| - * ('external' 'static'?)? functionSignature functionBody
|
| - * | 'external'? functionSignature ';'
|
| + * This method assumes that the current token matches `TokenType.OPEN_PAREN`.
|
| */
|
| - MethodDeclaration _parseMethodDeclarationAfterParameters(
|
| - CommentAndMetadata commentAndMetadata,
|
| - Token externalKeyword,
|
| - Token staticKeyword,
|
| - TypeName returnType,
|
| - SimpleIdentifier name,
|
| - TypeParameterList typeParameters,
|
| - FormalParameterList parameters) {
|
| - FunctionBody body = parseFunctionBody(
|
| - externalKeyword != null || staticKeyword == null,
|
| - ParserErrorCode.MISSING_FUNCTION_BODY,
|
| - false);
|
| - if (externalKeyword != null) {
|
| - if (body is! EmptyFunctionBody) {
|
| - _reportErrorForNode(ParserErrorCode.EXTERNAL_METHOD_WITH_BODY, body);
|
| - }
|
| - } else if (staticKeyword != null) {
|
| - if (body is EmptyFunctionBody && _parseFunctionBodies) {
|
| - _reportErrorForNode(ParserErrorCode.ABSTRACT_STATIC_METHOD, body);
|
| + FormalParameterList _parseFormalParameterListUnchecked() {
|
| + return _parseFormalParameterListAfterParen(getAndAdvance());
|
| + }
|
| +
|
| + /**
|
| + * Parse a function declaration statement. The [commentAndMetadata] is the
|
| + * documentation comment and metadata to be associated with the declaration.
|
| + * The [returnType] is the return type, or `null` if there is no return type.
|
| + * Return the function declaration statement that was parsed.
|
| + *
|
| + * functionDeclarationStatement ::=
|
| + * functionSignature functionBody
|
| + */
|
| + Statement _parseFunctionDeclarationStatementAfterReturnType(
|
| + CommentAndMetadata commentAndMetadata, TypeName returnType) {
|
| + FunctionDeclaration declaration =
|
| + parseFunctionDeclaration(commentAndMetadata, null, returnType);
|
| + Token propertyKeyword = declaration.propertyKeyword;
|
| + if (propertyKeyword != null) {
|
| + if (propertyKeyword.keyword == Keyword.GET) {
|
| + _reportErrorForToken(
|
| + ParserErrorCode.GETTER_IN_FUNCTION, propertyKeyword);
|
| + } else {
|
| + _reportErrorForToken(
|
| + ParserErrorCode.SETTER_IN_FUNCTION, propertyKeyword);
|
| }
|
| }
|
| - return new MethodDeclaration(
|
| - commentAndMetadata.comment,
|
| - commentAndMetadata.metadata,
|
| - externalKeyword,
|
| - staticKeyword,
|
| - returnType,
|
| - null,
|
| - null,
|
| - name,
|
| - typeParameters,
|
| - parameters,
|
| - body);
|
| + return new FunctionDeclarationStatement(declaration);
|
| }
|
|
|
| /**
|
| - * Parse a method declaration. The [commentAndMetadata] is the documentation
|
| - * comment and metadata to be associated with the declaration. The
|
| - * [externalKeyword] is the 'external' token. The [staticKeyword] is the
|
| - * static keyword, or `null` if the getter is not static. The [returnType] is
|
| - * the return type of the method. Return the method declaration that was
|
| - * parsed.
|
| + * Parse a function type alias. The [commentAndMetadata] is the metadata to be
|
| + * associated with the member. The [keyword] is the token representing the
|
| + * 'typedef' keyword. Return the function type alias that was parsed.
|
| *
|
| - * functionDeclaration ::=
|
| - * 'external'? 'static'? functionSignature functionBody
|
| - * | 'external'? functionSignature ';'
|
| + * functionTypeAlias ::=
|
| + * functionPrefix typeParameterList? formalParameterList ';'
|
| + *
|
| + * functionPrefix ::=
|
| + * returnType? name
|
| */
|
| - MethodDeclaration _parseMethodDeclarationAfterReturnType(
|
| - CommentAndMetadata commentAndMetadata,
|
| - Token externalKeyword,
|
| - Token staticKeyword,
|
| - TypeName returnType) {
|
| - SimpleIdentifier methodName = parseSimpleIdentifier(isDeclaration: true);
|
| - TypeParameterList typeParameters = _parseGenericMethodTypeParameters();
|
| - FormalParameterList parameters;
|
| + FunctionTypeAlias _parseFunctionTypeAlias(
|
| + CommentAndMetadata commentAndMetadata, Token keyword) {
|
| + TypeName returnType = null;
|
| + if (hasReturnTypeInTypeAlias) {
|
| + returnType = parseReturnType();
|
| + }
|
| + SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
|
| + TypeParameterList typeParameters = null;
|
| + if (_matches(TokenType.LT)) {
|
| + typeParameters = parseTypeParameterList();
|
| + }
|
| TokenType type = _currentToken.type;
|
| - // TODO(brianwilkerson) Figure out why we care what the current token is if
|
| - // it isn't a paren.
|
| - if (type != TokenType.OPEN_PAREN &&
|
| - (type == TokenType.OPEN_CURLY_BRACKET || type == TokenType.FUNCTION)) {
|
| - _reportErrorForToken(
|
| - ParserErrorCode.MISSING_METHOD_PARAMETERS, _currentToken.previous);
|
| - parameters = new FormalParameterList(
|
| + if (type == TokenType.SEMICOLON || type == TokenType.EOF) {
|
| + _reportErrorForCurrentToken(ParserErrorCode.MISSING_TYPEDEF_PARAMETERS);
|
| + FormalParameterList parameters = new FormalParameterList(
|
| _createSyntheticToken(TokenType.OPEN_PAREN),
|
| null,
|
| null,
|
| null,
|
| _createSyntheticToken(TokenType.CLOSE_PAREN));
|
| + Token semicolon = _expect(TokenType.SEMICOLON);
|
| + return new FunctionTypeAlias(
|
| + commentAndMetadata.comment,
|
| + commentAndMetadata.metadata,
|
| + keyword,
|
| + returnType,
|
| + name,
|
| + typeParameters,
|
| + parameters,
|
| + semicolon);
|
| + } else if (type == TokenType.OPEN_PAREN) {
|
| + FormalParameterList parameters = _parseFormalParameterListUnchecked();
|
| + _validateFormalParameterList(parameters);
|
| + Token semicolon = _expect(TokenType.SEMICOLON);
|
| + return new FunctionTypeAlias(
|
| + commentAndMetadata.comment,
|
| + commentAndMetadata.metadata,
|
| + keyword,
|
| + returnType,
|
| + name,
|
| + typeParameters,
|
| + parameters,
|
| + semicolon);
|
| } else {
|
| - parameters = parseFormalParameterList();
|
| + _reportErrorForCurrentToken(ParserErrorCode.MISSING_TYPEDEF_PARAMETERS);
|
| + // Recovery: At the very least we should skip to the start of the next
|
| + // valid compilation unit member, allowing for the possibility of finding
|
| + // the typedef parameters before that point.
|
| + return new FunctionTypeAlias(
|
| + commentAndMetadata.comment,
|
| + commentAndMetadata.metadata,
|
| + keyword,
|
| + returnType,
|
| + name,
|
| + typeParameters,
|
| + new FormalParameterList(_createSyntheticToken(TokenType.OPEN_PAREN),
|
| + null, null, null, _createSyntheticToken(TokenType.CLOSE_PAREN)),
|
| + _createSyntheticToken(TokenType.SEMICOLON));
|
| }
|
| - _validateFormalParameterList(parameters);
|
| - return _parseMethodDeclarationAfterParameters(
|
| - commentAndMetadata,
|
| - externalKeyword,
|
| - staticKeyword,
|
| - returnType,
|
| - methodName,
|
| - typeParameters,
|
| - parameters);
|
| }
|
|
|
| /**
|
| - * Parse the modifiers preceding a declaration. This method allows the
|
| - * modifiers to appear in any order but does generate errors for duplicated
|
| - * modifiers. Checks for other problems, such as having the modifiers appear
|
| - * in the wrong order or specifying both 'const' and 'final', are reported in
|
| - * one of the methods whose name is prefixed with `validateModifiersFor`.
|
| - * Return the modifiers that were parsed.
|
| + * Parses generic type parameters from a comment.
|
| *
|
| - * modifiers ::=
|
| - * ('abstract' | 'const' | 'external' | 'factory' | 'final' | 'static' | 'var')*
|
| - */
|
| - Modifiers _parseModifiers() {
|
| - Modifiers modifiers = new Modifiers();
|
| - bool progress = true;
|
| - while (progress) {
|
| - TokenType nextType = _peek().type;
|
| - if (nextType == TokenType.PERIOD ||
|
| - nextType == TokenType.LT ||
|
| - nextType == TokenType.OPEN_PAREN) {
|
| - return modifiers;
|
| - }
|
| - Keyword keyword = _currentToken.keyword;
|
| - if (keyword == Keyword.ABSTRACT) {
|
| - if (modifiers.abstractKeyword != null) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
|
| - _advance();
|
| - } else {
|
| - modifiers.abstractKeyword = getAndAdvance();
|
| - }
|
| - } else if (keyword == Keyword.CONST) {
|
| - if (modifiers.constKeyword != null) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
|
| - _advance();
|
| - } else {
|
| - modifiers.constKeyword = getAndAdvance();
|
| - }
|
| - } else if (keyword == Keyword.EXTERNAL) {
|
| - if (modifiers.externalKeyword != null) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
|
| - _advance();
|
| - } else {
|
| - modifiers.externalKeyword = getAndAdvance();
|
| - }
|
| - } else if (keyword == Keyword.FACTORY) {
|
| - if (modifiers.factoryKeyword != null) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
|
| - _advance();
|
| - } else {
|
| - modifiers.factoryKeyword = getAndAdvance();
|
| - }
|
| - } else if (keyword == Keyword.FINAL) {
|
| - if (modifiers.finalKeyword != null) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
|
| - _advance();
|
| - } else {
|
| - modifiers.finalKeyword = getAndAdvance();
|
| - }
|
| - } else if (keyword == Keyword.STATIC) {
|
| - if (modifiers.staticKeyword != null) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
|
| - _advance();
|
| - } else {
|
| - modifiers.staticKeyword = getAndAdvance();
|
| - }
|
| - } else if (keyword == Keyword.VAR) {
|
| - if (modifiers.varKeyword != null) {
|
| - _reportErrorForCurrentToken(
|
| - ParserErrorCode.DUPLICATED_MODIFIER, [_currentToken.lexeme]);
|
| - _advance();
|
| - } else {
|
| - modifiers.varKeyword = getAndAdvance();
|
| - }
|
| - } else {
|
| - progress = false;
|
| - }
|
| + * Normally this is handled by [_parseGenericMethodTypeParameters], but if the
|
| + * code already handles the normal generic type parameters, the comment
|
| + * matcher can be called directly. For example, we may have already tried
|
| + * matching `<` (less than sign) in a method declaration, and be currently
|
| + * on the `(` (open paren) because we didn't find it. In that case, this
|
| + * function will parse the preceding comment such as `/*<T, R>*/`.
|
| + */
|
| + TypeParameterList _parseGenericCommentTypeParameters() {
|
| + if (_injectGenericCommentTypeList()) {
|
| + return parseTypeParameterList();
|
| }
|
| - return modifiers;
|
| + return null;
|
| }
|
|
|
| /**
|
| - * Parse a class native clause. Return the native clause that was parsed.
|
| - *
|
| - * This method assumes that the current token matches `_NATIVE`.
|
| + * Parse the generic method or function's type parameters.
|
| *
|
| - * classNativeClause ::=
|
| - * 'native' name
|
| + * For backwards compatibility this can optionally use comments.
|
| + * See [parseGenericMethodComments].
|
| */
|
| - NativeClause _parseNativeClause() {
|
| - Token keyword = getAndAdvance();
|
| - StringLiteral name = parseStringLiteral();
|
| - return new NativeClause(keyword, name);
|
| + TypeParameterList _parseGenericMethodTypeParameters() {
|
| + if (parseGenericMethods && _matches(TokenType.LT) ||
|
| + _injectGenericCommentTypeList()) {
|
| + return parseTypeParameterList();
|
| + }
|
| + return null;
|
| }
|
|
|
| /**
|
| - * Parse a new expression. Return the new expression that was parsed.
|
| - *
|
| - * This method assumes that the current token matches `Keyword.NEW`.
|
| + * Parse a library name. The [missingNameError] is the error code to be used
|
| + * if the library name is missing. The [missingNameToken] is the token
|
| + * associated with the error produced if the library name is missing. Return
|
| + * the library name that was parsed.
|
| *
|
| - * newExpression ::=
|
| - * instanceCreationExpression
|
| + * libraryName ::=
|
| + * libraryIdentifier
|
| */
|
| - InstanceCreationExpression _parseNewExpression() =>
|
| - _parseInstanceCreationExpression(getAndAdvance());
|
| + LibraryIdentifier _parseLibraryName(
|
| + ParserErrorCode missingNameError, Token missingNameToken) {
|
| + if (_matchesIdentifier()) {
|
| + return parseLibraryIdentifier();
|
| + } else if (_matches(TokenType.STRING)) {
|
| + // Recovery: This should be extended to handle arbitrary tokens until we
|
| + // can find a token that can start a compilation unit member.
|
| + StringLiteral string = parseStringLiteral();
|
| + _reportErrorForNode(ParserErrorCode.NON_IDENTIFIER_LIBRARY_NAME, string);
|
| + } else {
|
| + _reportErrorForToken(missingNameError, missingNameToken);
|
| + }
|
| + return new LibraryIdentifier(
|
| + <SimpleIdentifier>[createSyntheticIdentifier()]);
|
| + }
|
|
|
| /**
|
| - * Parse a non-labeled statement. Return the non-labeled statement that was
|
| - * parsed.
|
| + * Parse a method declaration. The [commentAndMetadata] is the documentation
|
| + * comment and metadata to be associated with the declaration. The
|
| + * [externalKeyword] is the 'external' token. The [staticKeyword] is the
|
| + * static keyword, or `null` if the getter is not static. The [returnType] is
|
| + * the return type of the method. The [name] is the name of the method. The
|
| + * [parameters] is the parameters to the method. Return the method declaration
|
| + * that was parsed.
|
| *
|
| - * nonLabeledStatement ::=
|
| - * block
|
| - * | assertStatement
|
| - * | breakStatement
|
| - * | continueStatement
|
| - * | doStatement
|
| - * | forStatement
|
| - * | ifStatement
|
| - * | returnStatement
|
| - * | switchStatement
|
| - * | tryStatement
|
| - * | whileStatement
|
| - * | variableDeclarationList ';'
|
| - * | expressionStatement
|
| - * | functionSignature functionBody
|
| + * functionDeclaration ::=
|
| + * ('external' 'static'?)? functionSignature functionBody
|
| + * | 'external'? functionSignature ';'
|
| */
|
| - Statement _parseNonLabeledStatement() {
|
| - // TODO(brianwilkerson) Pass the comment and metadata on where appropriate.
|
| - CommentAndMetadata commentAndMetadata = parseCommentAndMetadata();
|
| - TokenType type = _currentToken.type;
|
| - if (type == TokenType.OPEN_CURLY_BRACKET) {
|
| - if (_tokenMatches(_peek(), TokenType.STRING)) {
|
| - Token afterString = skipStringLiteral(_currentToken.next);
|
| - if (afterString != null && afterString.type == TokenType.COLON) {
|
| - return new ExpressionStatement(
|
| - parseExpression2(), _expect(TokenType.SEMICOLON));
|
| - }
|
| - }
|
| - return parseBlock();
|
| - } else if (type == TokenType.KEYWORD &&
|
| - !_currentToken.keyword.isPseudoKeyword) {
|
| - Keyword keyword = _currentToken.keyword;
|
| - // TODO(jwren) compute some metrics to figure out a better order for this
|
| - // if-then sequence to optimize performance
|
| - if (keyword == Keyword.ASSERT) {
|
| - return parseAssertStatement();
|
| - } else if (keyword == Keyword.BREAK) {
|
| - return parseBreakStatement();
|
| - } else if (keyword == Keyword.CONTINUE) {
|
| - return parseContinueStatement();
|
| - } else if (keyword == Keyword.DO) {
|
| - return parseDoStatement();
|
| - } else if (keyword == Keyword.FOR) {
|
| - return parseForStatement();
|
| - } else if (keyword == Keyword.IF) {
|
| - return parseIfStatement();
|
| - } else if (keyword == Keyword.RETHROW) {
|
| - return new ExpressionStatement(
|
| - parseRethrowExpression(), _expect(TokenType.SEMICOLON));
|
| - } else if (keyword == Keyword.RETURN) {
|
| - return parseReturnStatement();
|
| - } else if (keyword == Keyword.SWITCH) {
|
| - return parseSwitchStatement();
|
| - } else if (keyword == Keyword.THROW) {
|
| - return new ExpressionStatement(
|
| - parseThrowExpression(), _expect(TokenType.SEMICOLON));
|
| - } else if (keyword == Keyword.TRY) {
|
| - return parseTryStatement();
|
| - } else if (keyword == Keyword.WHILE) {
|
| - return parseWhileStatement();
|
| - } else if (keyword == Keyword.VAR || keyword == Keyword.FINAL) {
|
| - return parseVariableDeclarationStatementAfterMetadata(
|
| - commentAndMetadata);
|
| - } else if (keyword == Keyword.VOID) {
|
| - TypeName returnType =
|
| - new TypeName(new SimpleIdentifier(getAndAdvance()), null);
|
| - Token next = _currentToken.next;
|
| - if (_matchesIdentifier() &&
|
| - next.matchesAny(const <TokenType>[
|
| - TokenType.OPEN_PAREN,
|
| - TokenType.OPEN_CURLY_BRACKET,
|
| - TokenType.FUNCTION,
|
| - TokenType.LT
|
| - ])) {
|
| - return _parseFunctionDeclarationStatementAfterReturnType(
|
| - commentAndMetadata, returnType);
|
| - } else {
|
| - //
|
| - // We have found an error of some kind. Try to recover.
|
| - //
|
| - if (_matchesIdentifier()) {
|
| - if (next.matchesAny(const <TokenType>[
|
| - TokenType.EQ,
|
| - TokenType.COMMA,
|
| - TokenType.SEMICOLON
|
| - ])) {
|
| - //
|
| - // We appear to have a variable declaration with a type of "void".
|
| - //
|
| - _reportErrorForNode(ParserErrorCode.VOID_VARIABLE, returnType);
|
| - return parseVariableDeclarationStatementAfterMetadata(
|
| - commentAndMetadata);
|
| - }
|
| - } else if (_matches(TokenType.CLOSE_CURLY_BRACKET)) {
|
| - //
|
| - // We appear to have found an incomplete statement at the end of a
|
| - // block. Parse it as a variable declaration.
|
| - //
|
| - return _parseVariableDeclarationStatementAfterType(
|
| - commentAndMetadata, null, returnType);
|
| - }
|
| - _reportErrorForCurrentToken(ParserErrorCode.MISSING_STATEMENT);
|
| - // TODO(brianwilkerson) Recover from this error.
|
| - return new EmptyStatement(_createSyntheticToken(TokenType.SEMICOLON));
|
| - }
|
| - } else if (keyword == Keyword.CONST) {
|
| - Token next = _currentToken.next;
|
| - if (next.matchesAny(const <TokenType>[
|
| - TokenType.LT,
|
| - TokenType.OPEN_CURLY_BRACKET,
|
| - TokenType.OPEN_SQUARE_BRACKET,
|
| - TokenType.INDEX
|
| - ])) {
|
| - return new ExpressionStatement(
|
| - parseExpression2(), _expect(TokenType.SEMICOLON));
|
| - } else if (_tokenMatches(next, TokenType.IDENTIFIER)) {
|
| - Token afterType = skipTypeName(next);
|
| - if (afterType != null) {
|
| - if (_tokenMatches(afterType, TokenType.OPEN_PAREN) ||
|
| - (_tokenMatches(afterType, TokenType.PERIOD) &&
|
| - _tokenMatches(afterType.next, TokenType.IDENTIFIER) &&
|
| - _tokenMatches(afterType.next.next, TokenType.OPEN_PAREN))) {
|
| - return new ExpressionStatement(
|
| - parseExpression2(), _expect(TokenType.SEMICOLON));
|
| - }
|
| - }
|
| - }
|
| - return parseVariableDeclarationStatementAfterMetadata(
|
| - commentAndMetadata);
|
| - } else if (keyword == Keyword.NEW ||
|
| - keyword == Keyword.TRUE ||
|
| - keyword == Keyword.FALSE ||
|
| - keyword == Keyword.NULL ||
|
| - keyword == Keyword.SUPER ||
|
| - keyword == Keyword.THIS) {
|
| - return new ExpressionStatement(
|
| - parseExpression2(), _expect(TokenType.SEMICOLON));
|
| - } else {
|
| - //
|
| - // We have found an error of some kind. Try to recover.
|
| - //
|
| - _reportErrorForCurrentToken(ParserErrorCode.MISSING_STATEMENT);
|
| - return new EmptyStatement(_createSyntheticToken(TokenType.SEMICOLON));
|
| - }
|
| - } else if (_inGenerator && _matchesString(_YIELD)) {
|
| - return parseYieldStatement();
|
| - } else if (_inAsync && _matchesString(_AWAIT)) {
|
| - if (_tokenMatchesKeyword(_peek(), Keyword.FOR)) {
|
| - return parseForStatement();
|
| + MethodDeclaration _parseMethodDeclarationAfterParameters(
|
| + CommentAndMetadata commentAndMetadata,
|
| + Token externalKeyword,
|
| + Token staticKeyword,
|
| + TypeName returnType,
|
| + SimpleIdentifier name,
|
| + TypeParameterList typeParameters,
|
| + FormalParameterList parameters) {
|
| + FunctionBody body = parseFunctionBody(
|
| + externalKeyword != null || staticKeyword == null,
|
| + ParserErrorCode.MISSING_FUNCTION_BODY,
|
| + false);
|
| + if (externalKeyword != null) {
|
| + if (body is! EmptyFunctionBody) {
|
| + _reportErrorForNode(ParserErrorCode.EXTERNAL_METHOD_WITH_BODY, body);
|
| }
|
| - return new ExpressionStatement(
|
| - parseExpression2(), _expect(TokenType.SEMICOLON));
|
| - } else if (_matchesString(_AWAIT) &&
|
| - _tokenMatchesKeyword(_peek(), Keyword.FOR)) {
|
| - Token awaitToken = _currentToken;
|
| - Statement statement = parseForStatement();
|
| - if (statement is! ForStatement) {
|
| - _reportErrorForToken(
|
| - CompileTimeErrorCode.ASYNC_FOR_IN_WRONG_CONTEXT, awaitToken);
|
| + } else if (staticKeyword != null) {
|
| + if (body is EmptyFunctionBody && _parseFunctionBodies) {
|
| + _reportErrorForNode(ParserErrorCode.ABSTRACT_STATIC_METHOD, body);
|
| }
|
| - return statement;
|
| - } else if (type == TokenType.SEMICOLON) {
|
| - return parseEmptyStatement();
|
| - } else if (isInitializedVariableDeclaration()) {
|
| - return parseVariableDeclarationStatementAfterMetadata(commentAndMetadata);
|
| - } else if (isFunctionDeclaration()) {
|
| - return parseFunctionDeclarationStatement();
|
| - } else if (type == TokenType.CLOSE_CURLY_BRACKET) {
|
| - _reportErrorForCurrentToken(ParserErrorCode.MISSING_STATEMENT);
|
| - return new EmptyStatement(_createSyntheticToken(TokenType.SEMICOLON));
|
| + }
|
| + return new MethodDeclaration(
|
| + commentAndMetadata.comment,
|
| + commentAndMetadata.metadata,
|
| + externalKeyword,
|
| + staticKeyword,
|
| + returnType,
|
| + null,
|
| + null,
|
| + name,
|
| + typeParameters,
|
| + parameters,
|
| + body);
|
| + }
|
| +
|
| + /**
|
| + * Parse a method declaration. The [commentAndMetadata] is the documentation
|
| + * comment and metadata to be associated with the declaration. The
|
| + * [externalKeyword] is the 'external' token. The [staticKeyword] is the
|
| + * static keyword, or `null` if the getter is not static. The [returnType] is
|
| + * the return type of the method. Return the method declaration that was
|
| + * parsed.
|
| + *
|
| + * functionDeclaration ::=
|
| + * 'external'? 'static'? functionSignature functionBody
|
| + * | 'external'? functionSignature ';'
|
| + */
|
| + MethodDeclaration _parseMethodDeclarationAfterReturnType(
|
| + CommentAndMetadata commentAndMetadata,
|
| + Token externalKeyword,
|
| + Token staticKeyword,
|
| + TypeName returnType) {
|
| + SimpleIdentifier methodName = parseSimpleIdentifier(isDeclaration: true);
|
| + TypeParameterList typeParameters = _parseGenericMethodTypeParameters();
|
| + FormalParameterList parameters;
|
| + TokenType type = _currentToken.type;
|
| + // TODO(brianwilkerson) Figure out why we care what the current token is if
|
| + // it isn't a paren.
|
| + if (type != TokenType.OPEN_PAREN &&
|
| + (type == TokenType.OPEN_CURLY_BRACKET || type == TokenType.FUNCTION)) {
|
| + _reportErrorForToken(
|
| + ParserErrorCode.MISSING_METHOD_PARAMETERS, _currentToken.previous);
|
| + parameters = new FormalParameterList(
|
| + _createSyntheticToken(TokenType.OPEN_PAREN),
|
| + null,
|
| + null,
|
| + null,
|
| + _createSyntheticToken(TokenType.CLOSE_PAREN));
|
| } else {
|
| - return new ExpressionStatement(
|
| - parseExpression2(), _expect(TokenType.SEMICOLON));
|
| + parameters = parseFormalParameterList();
|
| }
|
| + _validateFormalParameterList(parameters);
|
| + return _parseMethodDeclarationAfterParameters(
|
| + commentAndMetadata,
|
| + externalKeyword,
|
| + staticKeyword,
|
| + returnType,
|
| + methodName,
|
| + typeParameters,
|
| + parameters);
|
| + }
|
| +
|
| + /**
|
| + * Parse a class native clause. Return the native clause that was parsed.
|
| + *
|
| + * This method assumes that the current token matches `_NATIVE`.
|
| + *
|
| + * classNativeClause ::=
|
| + * 'native' name
|
| + */
|
| + NativeClause _parseNativeClause() {
|
| + Token keyword = getAndAdvance();
|
| + StringLiteral name = parseStringLiteral();
|
| + return new NativeClause(keyword, name);
|
| }
|
|
|
| /**
|
| @@ -7187,79 +6929,6 @@ class Parser {
|
| }
|
|
|
| /**
|
| - * Parse a part or part-of directive. The [commentAndMetadata] is the metadata
|
| - * to be associated with the directive. Return the part or part-of directive
|
| - * that was parsed.
|
| - *
|
| - * This method assumes that the current token matches `Keyword.PART`.
|
| - *
|
| - * partDirective ::=
|
| - * metadata 'part' stringLiteral ';'
|
| - *
|
| - * partOfDirective ::=
|
| - * metadata 'part' 'of' identifier ';'
|
| - */
|
| - Directive _parsePartOrPartOfDirective(CommentAndMetadata commentAndMetadata) {
|
| - if (_tokenMatchesString(_peek(), _OF)) {
|
| - return _parsePartOfDirective(commentAndMetadata);
|
| - }
|
| - return _parsePartDirective(commentAndMetadata);
|
| - }
|
| -
|
| - /**
|
| - * Parse a postfix expression. Return the postfix expression that was parsed.
|
| - *
|
| - * postfixExpression ::=
|
| - * assignableExpression postfixOperator
|
| - * | primary selector*
|
| - *
|
| - * selector ::=
|
| - * assignableSelector
|
| - * | argumentList
|
| - */
|
| - Expression _parsePostfixExpression() {
|
| - Expression operand = parseAssignableExpression(true);
|
| - TokenType type = _currentToken.type;
|
| - if (type == TokenType.OPEN_SQUARE_BRACKET ||
|
| - type == TokenType.PERIOD ||
|
| - type == TokenType.QUESTION_PERIOD ||
|
| - type == TokenType.OPEN_PAREN ||
|
| - (parseGenericMethods && type == TokenType.LT)) {
|
| - do {
|
| - if (_isLikelyArgumentList()) {
|
| - TypeArgumentList typeArguments = _parseOptionalTypeArguments();
|
| - ArgumentList argumentList = parseArgumentList();
|
| - Expression currentOperand = operand;
|
| - if (currentOperand is PropertyAccess) {
|
| - operand = new MethodInvocation(
|
| - currentOperand.target,
|
| - currentOperand.operator,
|
| - currentOperand.propertyName,
|
| - typeArguments,
|
| - argumentList);
|
| - } else {
|
| - operand = new FunctionExpressionInvocation(
|
| - operand, typeArguments, argumentList);
|
| - }
|
| - } else {
|
| - operand = _parseAssignableSelector(operand, true);
|
| - }
|
| - type = _currentToken.type;
|
| - } while (type == TokenType.OPEN_SQUARE_BRACKET ||
|
| - type == TokenType.PERIOD ||
|
| - type == TokenType.QUESTION_PERIOD ||
|
| - type == TokenType.OPEN_PAREN);
|
| - return operand;
|
| - }
|
| - if (!_currentToken.type.isIncrementOperator) {
|
| - return operand;
|
| - }
|
| - _ensureAssignable(operand);
|
| - Token operator = getAndAdvance();
|
| - return new PostfixExpression(operand, operator);
|
| - }
|
| -
|
| - /**
|
| * Parse a prefixed identifier given that the given [qualifier] was already
|
| * parsed. Return the prefixed identifier that was parsed.
|
| *
|
| @@ -7291,79 +6960,6 @@ class Parser {
|
| }
|
|
|
| /**
|
| - * Parse a redirecting constructor invocation. The flag [hasPeriod] should be
|
| - * `true` if the `this` is followed by a period. Return the redirecting
|
| - * constructor invocation that was parsed.
|
| - *
|
| - * This method assumes that the current token matches `Keyword.THIS`.
|
| - *
|
| - * redirectingConstructorInvocation ::=
|
| - * 'this' ('.' identifier)? arguments
|
| - */
|
| - RedirectingConstructorInvocation _parseRedirectingConstructorInvocation(
|
| - bool hasPeriod) {
|
| - Token keyword = getAndAdvance();
|
| - Token period = null;
|
| - SimpleIdentifier constructorName = null;
|
| - if (hasPeriod) {
|
| - period = getAndAdvance();
|
| - if (_matchesIdentifier()) {
|
| - constructorName = _parseSimpleIdentifierUnchecked(isDeclaration: false);
|
| - } else {
|
| - _reportErrorForCurrentToken(ParserErrorCode.MISSING_IDENTIFIER);
|
| - constructorName = createSyntheticIdentifier(isDeclaration: false);
|
| - _advance();
|
| - }
|
| - }
|
| - ArgumentList argumentList = _parseArgumentListChecked();
|
| - return new RedirectingConstructorInvocation(
|
| - keyword, period, constructorName, argumentList);
|
| - }
|
| -
|
| - /**
|
| - * Parse a setter. The [commentAndMetadata] is the documentation comment and
|
| - * metadata to be associated with the declaration. The [externalKeyword] is
|
| - * the 'external' token. The [staticKeyword] is the static keyword, or `null`
|
| - * if the setter is not static. The [returnType] is the return type that has
|
| - * already been parsed, or `null` if there was no return type. Return the
|
| - * setter that was parsed.
|
| - *
|
| - * This method assumes that the current token matches `Keyword.SET`.
|
| - *
|
| - * setter ::=
|
| - * setterSignature functionBody?
|
| - *
|
| - * setterSignature ::=
|
| - * 'external'? 'static'? returnType? 'set' identifier formalParameterList
|
| - */
|
| - MethodDeclaration _parseSetter(CommentAndMetadata commentAndMetadata,
|
| - Token externalKeyword, Token staticKeyword, TypeName returnType) {
|
| - Token propertyKeyword = getAndAdvance();
|
| - SimpleIdentifier name = parseSimpleIdentifier(isDeclaration: true);
|
| - FormalParameterList parameters = parseFormalParameterList();
|
| - _validateFormalParameterList(parameters);
|
| - FunctionBody body = parseFunctionBody(
|
| - externalKeyword != null || staticKeyword == null,
|
| - ParserErrorCode.STATIC_SETTER_WITHOUT_BODY,
|
| - false);
|
| - if (externalKeyword != null && body is! EmptyFunctionBody) {
|
| - _reportErrorForCurrentToken(ParserErrorCode.EXTERNAL_SETTER_WITH_BODY);
|
| - }
|
| - return new MethodDeclaration(
|
| - commentAndMetadata.comment,
|
| - commentAndMetadata.metadata,
|
| - externalKeyword,
|
| - staticKeyword,
|
| - returnType,
|
| - propertyKeyword,
|
| - null,
|
| - name,
|
| - null,
|
| - parameters,
|
| - body);
|
| - }
|
| -
|
| - /**
|
| * Parse a simple identifier. Return the simple identifier that was parsed.
|
| *
|
| * This method assumes that the current token matches an identifier.
|
| @@ -7484,55 +7080,6 @@ class Parser {
|
| return strings.length == 1 ? strings[0] : new AdjacentStrings(strings);
|
| }
|
|
|
| - /**
|
| - * Parse a type alias. The [commentAndMetadata] is the metadata to be
|
| - * associated with the member. Return the type alias that was parsed.
|
| - *
|
| - * This method assumes that the current token matches [Keyword.TYPEDEF].
|
| - *
|
| - * typeAlias ::=
|
| - * 'typedef' typeAliasBody
|
| - *
|
| - * typeAliasBody ::=
|
| - * classTypeAlias
|
| - * | functionTypeAlias
|
| - *
|
| - * classTypeAlias ::=
|
| - * identifier typeParameters? '=' 'abstract'? mixinApplication
|
| - *
|
| - * mixinApplication ::=
|
| - * qualified withClause implementsClause? ';'
|
| - *
|
| - * functionTypeAlias ::=
|
| - * functionPrefix typeParameterList? formalParameterList ';'
|
| - *
|
| - * functionPrefix ::=
|
| - * returnType? name
|
| - */
|
| - TypeAlias _parseTypeAlias(CommentAndMetadata commentAndMetadata) {
|
| - Token keyword = getAndAdvance();
|
| - if (_matchesIdentifier()) {
|
| - Token next = _peek();
|
| - if (_tokenMatches(next, TokenType.LT)) {
|
| - next = _skipTypeParameterList(next);
|
| - if (next != null && _tokenMatches(next, TokenType.EQ)) {
|
| - TypeAlias typeAlias =
|
| - parseClassTypeAlias(commentAndMetadata, null, keyword);
|
| - _reportErrorForToken(
|
| - ParserErrorCode.DEPRECATED_CLASS_TYPE_ALIAS, keyword);
|
| - return typeAlias;
|
| - }
|
| - } else if (_tokenMatches(next, TokenType.EQ)) {
|
| - TypeAlias typeAlias =
|
| - parseClassTypeAlias(commentAndMetadata, null, keyword);
|
| - _reportErrorForToken(
|
| - ParserErrorCode.DEPRECATED_CLASS_TYPE_ALIAS, keyword);
|
| - return typeAlias;
|
| - }
|
| - }
|
| - return _parseFunctionTypeAlias(commentAndMetadata, keyword);
|
| - }
|
| -
|
| TypeName _parseTypeName(bool inExpression) {
|
| Identifier typeName;
|
| if (_matchesIdentifier()) {
|
|
|