| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library fasta.parser.parser; |
| |
| import '../scanner.dart' show ErrorToken; |
| |
| import '../scanner/recover.dart' show closeBraceFor, skipToEof; |
| |
| import '../scanner/keyword.dart' show Keyword; |
| |
| import '../scanner/precedence.dart' |
| show |
| ASSIGNMENT_PRECEDENCE, |
| AS_INFO, |
| CASCADE_PRECEDENCE, |
| EQUALITY_PRECEDENCE, |
| GT_INFO, |
| IS_INFO, |
| MINUS_MINUS_INFO, |
| OPEN_PAREN_INFO, |
| OPEN_SQUARE_BRACKET_INFO, |
| PERIOD_INFO, |
| PLUS_PLUS_INFO, |
| POSTFIX_PRECEDENCE, |
| PrecedenceInfo, |
| QUESTION_INFO, |
| QUESTION_PERIOD_INFO, |
| RELATIONAL_PRECEDENCE, |
| SCRIPT_INFO; |
| |
| import '../scanner/token.dart' |
| show |
| BeginGroupToken, |
| KeywordToken, |
| SymbolToken, |
| Token, |
| isUserDefinableOperator; |
| |
| import '../scanner/token_constants.dart' |
| show |
| COMMA_TOKEN, |
| DOUBLE_TOKEN, |
| EOF_TOKEN, |
| EQ_TOKEN, |
| FUNCTION_TOKEN, |
| GT_TOKEN, |
| GT_GT_TOKEN, |
| HASH_TOKEN, |
| HEXADECIMAL_TOKEN, |
| IDENTIFIER_TOKEN, |
| INT_TOKEN, |
| KEYWORD_TOKEN, |
| LT_TOKEN, |
| OPEN_CURLY_BRACKET_TOKEN, |
| OPEN_PAREN_TOKEN, |
| OPEN_SQUARE_BRACKET_TOKEN, |
| PERIOD_TOKEN, |
| SEMICOLON_TOKEN, |
| STRING_INTERPOLATION_IDENTIFIER_TOKEN, |
| STRING_INTERPOLATION_TOKEN, |
| STRING_TOKEN; |
| |
| import '../scanner/characters.dart' show $CLOSE_CURLY_BRACKET; |
| |
| import '../util/link.dart' show Link; |
| |
| import 'listener.dart' show Listener; |
| |
| import 'error_kind.dart' show ErrorKind; |
| |
| import 'identifier_context.dart' show IdentifierContext; |
| |
| /// Returns true if [token] is the symbol or keyword [value]. |
| bool optional(String value, Token token) { |
| return identical(value, token.stringValue); |
| } |
| |
| class FormalParameterType { |
| final String type; |
| const FormalParameterType(this.type); |
| bool get isRequired => this == REQUIRED; |
| bool get isPositional => this == POSITIONAL; |
| bool get isNamed => this == NAMED; |
| static final REQUIRED = const FormalParameterType('required'); |
| static final POSITIONAL = const FormalParameterType('positional'); |
| static final NAMED = const FormalParameterType('named'); |
| } |
| |
| /** |
| * An event generating parser of Dart programs. This parser expects |
| * all tokens in a linked list (aka a token stream). |
| * |
| * The class [Scanner] is used to generate a token stream. See the |
| * file scanner.dart. |
| * |
| * Subclasses of the class [Listener] are used to listen to events. |
| * |
| * Most methods of this class belong in one of two major categories: |
| * parse methods and peek methods. Parse methods all have the prefix |
| * parse, and peek methods all have the prefix peek. |
| * |
| * Parse methods generate events (by calling methods on [listener]) |
| * and return the next token to parse. Peek methods do not generate |
| * events (except for errors) and may return null. |
| * |
| * Parse methods are generally named parseGrammarProductionSuffix. The |
| * suffix can be one of "opt", or "star". "opt" means zero or one |
| * matches, "star" means zero or more matches. For example, |
| * [parseMetadataStar] corresponds to this grammar snippet: [: |
| * metadata* :], and [parseTypeOpt] corresponds to: [: type? :]. |
| */ |
| class Parser { |
| final Listener listener; |
| |
| bool mayParseFunctionExpressions = true; |
| |
| bool asyncAwaitKeywordsEnabled; |
| |
| Parser(this.listener, {this.asyncAwaitKeywordsEnabled: false}); |
| |
| Token parseUnit(Token token) { |
| listener.beginCompilationUnit(token); |
| int count = 0; |
| while (!identical(token.kind, EOF_TOKEN)) { |
| token = parseTopLevelDeclaration(token); |
| count++; |
| } |
| listener.endCompilationUnit(count, token); |
| return token; |
| } |
| |
| Token parseTopLevelDeclaration(Token token) { |
| token = _parseTopLevelDeclaration(token); |
| listener.endTopLevelDeclaration(token); |
| return token; |
| } |
| |
| Token _parseTopLevelDeclaration(Token token) { |
| if (identical(token.info, SCRIPT_INFO)) { |
| return parseScript(token); |
| } |
| token = parseMetadataStar(token); |
| final String value = token.stringValue; |
| if ((identical(value, 'abstract') && optional('class', token.next)) || |
| identical(value, 'class')) { |
| return parseClassOrNamedMixinApplication(token); |
| } else if (identical(value, 'enum')) { |
| return parseEnum(token); |
| } else if (identical(value, 'typedef')) { |
| return parseTypedef(token); |
| } else if (identical(value, 'library')) { |
| return parseLibraryName(token); |
| } else if (identical(value, 'import')) { |
| return parseImport(token); |
| } else if (identical(value, 'export')) { |
| return parseExport(token); |
| } else if (identical(value, 'part')) { |
| return parsePartOrPartOf(token); |
| } else { |
| return parseTopLevelMember(token); |
| } |
| } |
| |
| /// library qualified ';' |
| Token parseLibraryName(Token token) { |
| Token libraryKeyword = token; |
| listener.beginLibraryName(libraryKeyword); |
| assert(optional('library', token)); |
| token = parseQualified(token.next, IdentifierContext.libraryName, |
| IdentifierContext.libraryNameContinuation); |
| Token semicolon = token; |
| token = expect(';', token); |
| listener.endLibraryName(libraryKeyword, semicolon); |
| return token; |
| } |
| |
| /// import uri (if (test) uri)* (as identifier)? combinator* ';' |
| Token parseImport(Token token) { |
| Token importKeyword = token; |
| listener.beginImport(importKeyword); |
| assert(optional('import', token)); |
| token = parseLiteralStringOrRecoverExpression(token.next); |
| token = parseConditionalUris(token); |
| Token deferredKeyword; |
| if (optional('deferred', token)) { |
| deferredKeyword = token; |
| token = token.next; |
| } |
| Token asKeyword; |
| if (optional('as', token)) { |
| asKeyword = token; |
| token = parseIdentifier( |
| token.next, IdentifierContext.importPrefixDeclaration); |
| } |
| token = parseCombinators(token); |
| Token semicolon = token; |
| token = expect(';', token); |
| listener.endImport(importKeyword, deferredKeyword, asKeyword, semicolon); |
| return token; |
| } |
| |
| /// if (test) uri |
| Token parseConditionalUris(Token token) { |
| listener.beginConditionalUris(token); |
| int count = 0; |
| while (optional('if', token)) { |
| count++; |
| token = parseConditionalUri(token); |
| } |
| listener.endConditionalUris(count); |
| return token; |
| } |
| |
| Token parseConditionalUri(Token token) { |
| listener.beginConditionalUri(token); |
| Token ifKeyword = token; |
| token = expect('if', token); |
| token = expect('(', token); |
| token = parseDottedName(token); |
| Token equalitySign; |
| if (optional('==', token)) { |
| equalitySign = token; |
| token = parseLiteralStringOrRecoverExpression(token.next); |
| } |
| token = expect(')', token); |
| token = parseLiteralStringOrRecoverExpression(token); |
| listener.endConditionalUri(ifKeyword, equalitySign); |
| return token; |
| } |
| |
| Token parseDottedName(Token token) { |
| listener.beginDottedName(token); |
| Token firstIdentifier = token; |
| token = parseIdentifier(token, IdentifierContext.dottedName); |
| int count = 1; |
| while (optional('.', token)) { |
| token = |
| parseIdentifier(token.next, IdentifierContext.dottedNameContinuation); |
| count++; |
| } |
| listener.endDottedName(count, firstIdentifier); |
| return token; |
| } |
| |
| /// export uri conditional-uris* combinator* ';' |
| Token parseExport(Token token) { |
| Token exportKeyword = token; |
| listener.beginExport(exportKeyword); |
| assert(optional('export', token)); |
| token = parseLiteralStringOrRecoverExpression(token.next); |
| token = parseConditionalUris(token); |
| token = parseCombinators(token); |
| Token semicolon = token; |
| token = expect(';', token); |
| listener.endExport(exportKeyword, semicolon); |
| return token; |
| } |
| |
| Token parseCombinators(Token token) { |
| listener.beginCombinators(token); |
| int count = 0; |
| while (true) { |
| String value = token.stringValue; |
| if (identical('hide', value)) { |
| token = parseHide(token); |
| } else if (identical('show', value)) { |
| token = parseShow(token); |
| } else { |
| listener.endCombinators(count); |
| break; |
| } |
| count++; |
| } |
| return token; |
| } |
| |
| /// hide identifierList |
| Token parseHide(Token token) { |
| Token hideKeyword = token; |
| listener.beginHide(hideKeyword); |
| assert(optional('hide', token)); |
| token = parseIdentifierList(token.next); |
| listener.endHide(hideKeyword); |
| return token; |
| } |
| |
| /// show identifierList |
| Token parseShow(Token token) { |
| Token showKeyword = token; |
| listener.beginShow(showKeyword); |
| assert(optional('show', token)); |
| token = parseIdentifierList(token.next); |
| listener.endShow(showKeyword); |
| return token; |
| } |
| |
| /// identifier (, identifier)* |
| Token parseIdentifierList(Token token) { |
| listener.beginIdentifierList(token); |
| token = parseIdentifier(token, IdentifierContext.combinator); |
| int count = 1; |
| while (optional(',', token)) { |
| token = parseIdentifier(token.next, IdentifierContext.combinator); |
| count++; |
| } |
| listener.endIdentifierList(count); |
| return token; |
| } |
| |
| /// type (, type)* |
| Token parseTypeList(Token token) { |
| listener.beginTypeList(token); |
| token = parseType(token); |
| int count = 1; |
| while (optional(',', token)) { |
| token = parseType(token.next); |
| count++; |
| } |
| listener.endTypeList(count); |
| return token; |
| } |
| |
| Token parsePartOrPartOf(Token token) { |
| assert(optional('part', token)); |
| if (optional('of', token.next)) { |
| return parsePartOf(token); |
| } else { |
| return parsePart(token); |
| } |
| } |
| |
| Token parsePart(Token token) { |
| Token partKeyword = token; |
| listener.beginPart(token); |
| assert(optional('part', token)); |
| token = parseLiteralStringOrRecoverExpression(token.next); |
| Token semicolon = token; |
| token = expect(';', token); |
| listener.endPart(partKeyword, semicolon); |
| return token; |
| } |
| |
| Token parsePartOf(Token token) { |
| listener.beginPartOf(token); |
| assert(optional('part', token)); |
| assert(optional('of', token.next)); |
| Token partKeyword = token; |
| token = token.next.next; |
| bool hasName = token.isIdentifier(); |
| if (hasName) { |
| token = parseQualified(token, IdentifierContext.partName, |
| IdentifierContext.partNameContinuation); |
| } else { |
| token = parseLiteralStringOrRecoverExpression(token); |
| } |
| Token semicolon = token; |
| token = expect(';', token); |
| listener.endPartOf(partKeyword, semicolon, hasName); |
| return token; |
| } |
| |
| Token parseMetadataStar(Token token, {bool forParameter: false}) { |
| listener.beginMetadataStar(token); |
| int count = 0; |
| while (optional('@', token)) { |
| token = parseMetadata(token); |
| count++; |
| } |
| listener.endMetadataStar(count, forParameter); |
| return token; |
| } |
| |
| /** |
| * Parse |
| * [: '@' qualified (‘.’ identifier)? (arguments)? :] |
| */ |
| Token parseMetadata(Token token) { |
| listener.beginMetadata(token); |
| Token atToken = token; |
| assert(optional('@', token)); |
| token = parseIdentifier(token.next, IdentifierContext.metadataReference); |
| token = |
| parseQualifiedRestOpt(token, IdentifierContext.metadataContinuation); |
| token = parseTypeArgumentsOpt(token); |
| Token period = null; |
| if (optional('.', token)) { |
| period = token; |
| token = parseIdentifier( |
| token.next, IdentifierContext.metadataContinuationAfterTypeArguments); |
| } |
| token = parseArgumentsOpt(token); |
| listener.endMetadata(atToken, period, token); |
| return token; |
| } |
| |
| Token parseScript(Token token) { |
| listener.handleScript(token); |
| return token.next; |
| } |
| |
| Token parseTypedef(Token token) { |
| Token typedefKeyword = token; |
| listener.beginFunctionTypeAlias(token); |
| Token equals; |
| if (optional('=', peekAfterNominalType(token.next))) { |
| token = parseIdentifier(token.next, IdentifierContext.typedefDeclaration); |
| token = parseTypeVariablesOpt(token); |
| equals = token; |
| token = expect('=', token); |
| token = parseType(token); |
| } else { |
| token = parseReturnTypeOpt(token.next); |
| token = parseIdentifier(token, IdentifierContext.typedefDeclaration); |
| token = parseTypeVariablesOpt(token); |
| token = parseFormalParameters(token); |
| } |
| listener.endFunctionTypeAlias(typedefKeyword, equals, token); |
| return expect(';', token); |
| } |
| |
| Token parseMixinApplication(Token token) { |
| listener.beginMixinApplication(token); |
| token = parseType(token); |
| Token withKeyword = token; |
| token = expect('with', token); |
| token = parseTypeList(token); |
| listener.endMixinApplication(withKeyword); |
| return token; |
| } |
| |
| Token parseReturnTypeOpt(Token token) { |
| if (identical(token.stringValue, 'void')) { |
| if (isGeneralizedFunctionType(token.next)) { |
| return parseType(token); |
| } else { |
| listener.handleVoidKeyword(token); |
| return token.next; |
| } |
| } else { |
| return parseTypeOpt(token); |
| } |
| } |
| |
| Token parseFormalParametersOpt(Token token) { |
| if (optional('(', token)) { |
| return parseFormalParameters(token); |
| } else { |
| listener.handleNoFormalParameters(token); |
| return token; |
| } |
| } |
| |
| Token skipFormalParameters(Token token) { |
| // TODO(ahe): Shouldn't this be `beginFormalParameters`? |
| listener.beginOptionalFormalParameters(token); |
| if (!optional('(', token)) { |
| if (optional(';', token)) { |
| reportRecoverableError(token, ErrorKind.ExpectedOpenParens); |
| return token; |
| } |
| return reportUnrecoverableError( |
| token, ErrorKind.ExpectedButGot, {"expected": "("})?.next; |
| } |
| BeginGroupToken beginGroupToken = token; |
| Token endToken = beginGroupToken.endGroup; |
| listener.endFormalParameters(0, token, endToken); |
| return endToken.next; |
| } |
| |
| /// Parses the formal parameter list of a function. |
| /// |
| /// If [inFunctionType] is true, then the names may be omitted (except for |
| /// named arguments). If it is false, then the types may be omitted. |
| Token parseFormalParameters(Token token, {bool inFunctionType: false}) { |
| Token begin = token; |
| listener.beginFormalParameters(begin); |
| expect('(', token); |
| int parameterCount = 0; |
| do { |
| token = token.next; |
| if (optional(')', token)) { |
| break; |
| } |
| ++parameterCount; |
| String value = token.stringValue; |
| if (identical(value, '[')) { |
| token = parseOptionalFormalParameters(token, false, |
| inFunctionType: inFunctionType); |
| break; |
| } else if (identical(value, '{')) { |
| token = parseOptionalFormalParameters(token, true, |
| inFunctionType: inFunctionType); |
| break; |
| } else if (identical(value, '[]')) { |
| --parameterCount; |
| reportRecoverableError(token, ErrorKind.EmptyOptionalParameterList); |
| token = token.next; |
| break; |
| } |
| token = parseFormalParameter(token, FormalParameterType.REQUIRED, |
| inFunctionType: inFunctionType); |
| } while (optional(',', token)); |
| listener.endFormalParameters(parameterCount, begin, token); |
| return expect(')', token); |
| } |
| |
| Token parseFormalParameter(Token token, FormalParameterType kind, |
| {bool inFunctionType: false}) { |
| token = parseMetadataStar(token, forParameter: true); |
| listener.beginFormalParameter(token); |
| |
| // Skip over `covariant` token, if the next token is an identifier or |
| // modifier. |
| // This enables the case where `covariant` is the name of the parameter: |
| // void foo(covariant); |
| Token covariantKeyword; |
| if (identical(token.stringValue, 'covariant') && |
| (token.next.isIdentifier() || isModifier(token.next))) { |
| covariantKeyword = token; |
| token = token.next; |
| } |
| token = parseModifiers(token); |
| bool isNamedParameter = kind == FormalParameterType.NAMED; |
| |
| Token thisKeyword = null; |
| if (inFunctionType && isNamedParameter) { |
| token = parseType(token); |
| token = |
| parseIdentifier(token, IdentifierContext.formalParameterDeclaration); |
| } else if (inFunctionType) { |
| token = parseType(token); |
| if (token.isIdentifier()) { |
| token = parseIdentifier( |
| token, IdentifierContext.formalParameterDeclaration); |
| } else { |
| listener.handleNoName(token); |
| } |
| } else { |
| token = parseReturnTypeOpt(token); |
| if (optional('this', token)) { |
| thisKeyword = token; |
| token = expect('.', token.next); |
| token = parseIdentifier(token, IdentifierContext.fieldInitializer); |
| } else { |
| token = parseIdentifier( |
| token, IdentifierContext.formalParameterDeclaration); |
| } |
| } |
| |
| if (optional('(', token)) { |
| Token inlineFunctionTypeStart = token; |
| listener.beginFunctionTypedFormalParameter(token); |
| listener.handleNoTypeVariables(token); |
| token = parseFormalParameters(token); |
| listener.endFunctionTypedFormalParameter( |
| covariantKeyword, thisKeyword, kind); |
| // Generalized function types don't allow inline function types. |
| // The following isn't allowed: |
| // int Function(int bar(String x)). |
| if (inFunctionType) { |
| reportRecoverableError( |
| inlineFunctionTypeStart, ErrorKind.InvalidInlineFunctionType); |
| } |
| } else if (optional('<', token)) { |
| Token inlineFunctionTypeStart = token; |
| listener.beginFunctionTypedFormalParameter(token); |
| token = parseTypeVariablesOpt(token); |
| token = parseFormalParameters(token); |
| listener.endFunctionTypedFormalParameter( |
| covariantKeyword, thisKeyword, kind); |
| // Generalized function types don't allow inline function types. |
| // The following isn't allowed: |
| // int Function(int bar(String x)). |
| if (inFunctionType) { |
| reportRecoverableError( |
| inlineFunctionTypeStart, ErrorKind.InvalidInlineFunctionType); |
| } |
| } |
| String value = token.stringValue; |
| if ((identical('=', value)) || (identical(':', value))) { |
| // TODO(ahe): Validate that these are only used for optional parameters. |
| Token equal = token; |
| token = parseExpression(token.next); |
| listener.handleValuedFormalParameter(equal, token); |
| if (kind.isRequired) { |
| reportRecoverableError(equal, ErrorKind.RequiredParameterWithDefault); |
| } else if (kind.isPositional && identical(':', value)) { |
| reportRecoverableError(equal, ErrorKind.PositionalParameterWithEquals); |
| } |
| } else { |
| listener.handleFormalParameterWithoutValue(token); |
| } |
| listener.endFormalParameter(covariantKeyword, thisKeyword, kind); |
| return token; |
| } |
| |
| Token parseOptionalFormalParameters(Token token, bool isNamed, |
| {bool inFunctionType: false}) { |
| Token begin = token; |
| listener.beginOptionalFormalParameters(begin); |
| assert((isNamed && optional('{', token)) || optional('[', token)); |
| int parameterCount = 0; |
| do { |
| token = token.next; |
| if (isNamed && optional('}', token)) { |
| break; |
| } else if (!isNamed && optional(']', token)) { |
| break; |
| } |
| var type = |
| isNamed ? FormalParameterType.NAMED : FormalParameterType.POSITIONAL; |
| token = parseFormalParameter(token, type, inFunctionType: inFunctionType); |
| ++parameterCount; |
| } while (optional(',', token)); |
| if (parameterCount == 0) { |
| reportRecoverableError( |
| token, |
| isNamed |
| ? ErrorKind.EmptyNamedParameterList |
| : ErrorKind.EmptyOptionalParameterList); |
| } |
| listener.endOptionalFormalParameters(parameterCount, begin, token); |
| if (isNamed) { |
| return expect('}', token); |
| } else { |
| return expect(']', token); |
| } |
| } |
| |
| Token parseTypeOpt(Token token) { |
| if (isGeneralizedFunctionType(token)) { |
| // Function type without return type. |
| return parseType(token); |
| } |
| Token peek = peekAfterIfType(token); |
| if (peek != null && (peek.isIdentifier() || optional('this', peek))) { |
| return parseType(token); |
| } |
| listener.handleNoType(token); |
| return token; |
| } |
| |
| bool isValidTypeReference(Token token) { |
| final kind = token.kind; |
| if (identical(kind, IDENTIFIER_TOKEN)) return true; |
| if (identical(kind, KEYWORD_TOKEN)) { |
| Keyword keyword = (token as KeywordToken).keyword; |
| String value = keyword.syntax; |
| return keyword.isPseudo || |
| (identical(value, 'dynamic')) || |
| (identical(value, 'void')); |
| } |
| return false; |
| } |
| |
| /// Returns true if [token] matches '<' type (',' type)* '>' '(', and |
| /// otherwise returns false. The final '(' is not part of the grammar |
| /// construct `typeArguments`, but it is required here such that type |
| /// arguments in generic method invocations can be recognized, and as few as |
| /// possible other constructs will pass (e.g., 'a < C, D > 3'). |
| bool isValidMethodTypeArguments(Token token) { |
| return tryParseMethodTypeArguments(token) != null; |
| } |
| |
| /// Returns token after match if [token] matches '<' type (',' type)* '>' '(', |
| /// and otherwise returns null. Does not produce listener events. With respect |
| /// to the final '(', please see the description of |
| /// [isValidMethodTypeArguments]. |
| Token tryParseMethodTypeArguments(Token token) { |
| if (!identical(token.kind, LT_TOKEN)) return null; |
| BeginGroupToken beginToken = token; |
| Token endToken = beginToken.endGroup; |
| if (endToken == null || !identical(endToken.next.kind, OPEN_PAREN_TOKEN)) { |
| return null; |
| } |
| token = tryParseType(token.next); |
| while (token != null && identical(token.kind, COMMA_TOKEN)) { |
| token = tryParseType(token.next); |
| } |
| if (token == null || !identical(token.kind, GT_TOKEN)) return null; |
| return token.next; |
| } |
| |
| /// Returns token after match if [token] matches typeName typeArguments?, and |
| /// otherwise returns null. Does not produce listener events. |
| Token tryParseType(Token token) { |
| token = tryParseQualified(token); |
| if (token == null) return null; |
| Token tokenAfterQualified = token; |
| token = tryParseNestedTypeArguments(token); |
| return token == null ? tokenAfterQualified : token; |
| } |
| |
| /// Returns token after match if [token] matches identifier ('.' identifier)?, |
| /// and otherwise returns null. Does not produce listener events. |
| Token tryParseQualified(Token token) { |
| if (!isValidTypeReference(token)) return null; |
| token = token.next; |
| if (!identical(token.kind, PERIOD_TOKEN)) return token; |
| token = token.next; |
| if (!identical(token.kind, IDENTIFIER_TOKEN)) return null; |
| return token.next; |
| } |
| |
| /// Returns token after match if [token] matches '<' type (',' type)* '>', |
| /// and otherwise returns null. Does not produce listener events. The final |
| /// '>' may be the first character in a '>>' token, in which case a synthetic |
| /// '>' token is created and returned, representing the second '>' in the |
| /// '>>' token. |
| Token tryParseNestedTypeArguments(Token token) { |
| if (!identical(token.kind, LT_TOKEN)) return null; |
| // If the initial '<' matches the first '>' in a '>>' token, we will have |
| // `token.endGroup == null`, so we cannot rely on `token.endGroup == null` |
| // to imply that the match must fail. Hence no `token.endGroup == null` |
| // test here. |
| token = tryParseType(token.next); |
| while (token != null && identical(token.kind, COMMA_TOKEN)) { |
| token = tryParseType(token.next); |
| } |
| if (token == null) return null; |
| if (identical(token.kind, GT_TOKEN)) return token.next; |
| if (!identical(token.kind, GT_GT_TOKEN)) return null; |
| // [token] is '>>' of which the final '>' that we are parsing is the first |
| // character. In order to keep the parsing process on track we must return |
| // a synthetic '>' corresponding to the second character of that '>>'. |
| Token syntheticToken = new SymbolToken(GT_INFO, token.charOffset + 1); |
| syntheticToken.next = token.next; |
| return syntheticToken; |
| } |
| |
| Token parseQualified(Token token, IdentifierContext context, |
| IdentifierContext continuationContext) { |
| token = parseIdentifier(token, context); |
| while (optional('.', token)) { |
| token = parseQualifiedRest(token, continuationContext); |
| } |
| return token; |
| } |
| |
| Token parseQualifiedRestOpt( |
| Token token, IdentifierContext continuationContext) { |
| if (optional('.', token)) { |
| return parseQualifiedRest(token, continuationContext); |
| } else { |
| return token; |
| } |
| } |
| |
| Token parseQualifiedRest(Token token, IdentifierContext context) { |
| assert(optional('.', token)); |
| Token period = token; |
| token = parseIdentifier(token.next, context); |
| listener.handleQualified(period); |
| return token; |
| } |
| |
| Token skipBlock(Token token) { |
| if (!optional('{', token)) { |
| return reportUnrecoverableError(token, ErrorKind.ExpectedBlockToSkip) |
| ?.next; |
| } |
| BeginGroupToken beginGroupToken = token; |
| Token endGroup = beginGroupToken.endGroup; |
| if (endGroup == null) { |
| return reportUnrecoverableError(beginGroupToken, ErrorKind.UnmatchedToken) |
| ?.next; |
| } else if (!identical(endGroup.kind, $CLOSE_CURLY_BRACKET)) { |
| return reportUnrecoverableError(beginGroupToken, ErrorKind.UnmatchedToken) |
| ?.next; |
| } |
| return beginGroupToken.endGroup; |
| } |
| |
| Token parseEnum(Token token) { |
| listener.beginEnum(token); |
| Token enumKeyword = token; |
| token = parseIdentifier(token.next, IdentifierContext.enumDeclaration); |
| token = expect('{', token); |
| int count = 0; |
| if (!optional('}', token)) { |
| token = parseIdentifier(token, IdentifierContext.enumValueDeclaration); |
| count++; |
| while (optional(',', token)) { |
| token = token.next; |
| if (optional('}', token)) break; |
| token = parseIdentifier(token, IdentifierContext.enumValueDeclaration); |
| count++; |
| } |
| } |
| Token endBrace = token; |
| token = expect('}', token); |
| listener.endEnum(enumKeyword, endBrace, count); |
| return token; |
| } |
| |
| Token parseClassOrNamedMixinApplication(Token token) { |
| Token begin = token; |
| Token abstractKeyword; |
| Token classKeyword = token; |
| if (optional('abstract', token)) { |
| abstractKeyword = token; |
| token = token.next; |
| classKeyword = token; |
| } |
| assert(optional('class', classKeyword)); |
| int modifierCount = 0; |
| if (abstractKeyword != null) { |
| parseModifier(abstractKeyword); |
| modifierCount++; |
| } |
| listener.handleModifiers(modifierCount); |
| bool isMixinApplication = optional('=', peekAfterNominalType(token)); |
| Token name = token.next; |
| |
| if (isMixinApplication) { |
| token = parseIdentifier(name, IdentifierContext.namedMixinDeclaration); |
| listener.beginNamedMixinApplication(begin, name); |
| } else { |
| token = parseIdentifier(name, IdentifierContext.classDeclaration); |
| listener.beginClassDeclaration(begin, name); |
| } |
| |
| token = parseTypeVariablesOpt(token); |
| |
| if (optional('=', token)) { |
| Token equals = token; |
| token = token.next; |
| return parseNamedMixinApplication( |
| token, begin, classKeyword, name, equals); |
| } else { |
| return parseClass(token, begin, classKeyword, name); |
| } |
| } |
| |
| Token parseNamedMixinApplication( |
| Token token, Token begin, Token classKeyword, Token name, Token equals) { |
| token = parseMixinApplication(token); |
| Token implementsKeyword = null; |
| if (optional('implements', token)) { |
| implementsKeyword = token; |
| token = parseTypeList(token.next); |
| } |
| listener.endNamedMixinApplication( |
| begin, classKeyword, equals, implementsKeyword, token); |
| return expect(';', token); |
| } |
| |
| Token parseClass(Token token, Token begin, Token classKeyword, Token name) { |
| Token extendsKeyword; |
| if (optional('extends', token)) { |
| extendsKeyword = token; |
| if (optional('with', peekAfterNominalType(token.next))) { |
| token = parseMixinApplication(token.next); |
| } else { |
| token = parseType(token.next); |
| } |
| } else { |
| extendsKeyword = null; |
| listener.handleNoType(token); |
| } |
| Token implementsKeyword; |
| int interfacesCount = 0; |
| if (optional('implements', token)) { |
| implementsKeyword = token; |
| do { |
| token = parseType(token.next); |
| ++interfacesCount; |
| } while (optional(',', token)); |
| } |
| token = parseClassBody(token); |
| listener.endClassDeclaration(interfacesCount, begin, classKeyword, |
| extendsKeyword, implementsKeyword, token); |
| return token.next; |
| } |
| |
| Token parseStringPart(Token token) { |
| if (token.kind != STRING_TOKEN) { |
| token = reportUnrecoverableError(token, ErrorKind.ExpectedString)?.next; |
| } |
| listener.handleStringPart(token); |
| return token.next; |
| } |
| |
| Token parseIdentifier(Token token, IdentifierContext context) { |
| if (!token.isIdentifier()) { |
| token = |
| reportUnrecoverableError(token, ErrorKind.ExpectedIdentifier)?.next; |
| } else if (token.isBuiltInIdentifier && |
| !context.isBuiltInIdentifierAllowed) { |
| if (context.inDeclaration) { |
| reportRecoverableError(token, ErrorKind.BuiltInIdentifierInDeclaration); |
| } else if (!optional("dynamic", token)) { |
| reportRecoverableError(token, ErrorKind.BuiltInIdentifierAsType); |
| } |
| } |
| listener.handleIdentifier(token, context); |
| return token.next; |
| } |
| |
| Token expect(String string, Token token) { |
| if (!identical(string, token.stringValue)) { |
| return reportUnrecoverableError( |
| token, ErrorKind.ExpectedButGot, {"expected": string})?.next; |
| } |
| return token.next; |
| } |
| |
| Token parseTypeVariable(Token token) { |
| listener.beginTypeVariable(token); |
| token = parseMetadataStar(token); |
| token = parseIdentifier(token, IdentifierContext.typeVariableDeclaration); |
| Token extendsOrSuper = null; |
| if (optional('extends', token) || optional('super', token)) { |
| extendsOrSuper = token; |
| token = parseType(token.next); |
| } else { |
| listener.handleNoType(token); |
| } |
| listener.endTypeVariable(token, extendsOrSuper); |
| return token; |
| } |
| |
| /** |
| * Returns true if the stringValue of the [token] is either [value1], |
| * [value2], or [value3]. |
| */ |
| bool isOneOf3(Token token, String value1, String value2, String value3) { |
| String stringValue = token.stringValue; |
| return value1 == stringValue || |
| value2 == stringValue || |
| value3 == stringValue; |
| } |
| |
| /** |
| * Returns true if the stringValue of the [token] is either [value1], |
| * [value2], [value3], or [value4]. |
| */ |
| bool isOneOf4( |
| Token token, String value1, String value2, String value3, String value4) { |
| String stringValue = token.stringValue; |
| return value1 == stringValue || |
| value2 == stringValue || |
| value3 == stringValue || |
| value4 == stringValue; |
| } |
| |
| bool notEofOrValue(String value, Token token) { |
| return !identical(token.kind, EOF_TOKEN) && |
| !identical(value, token.stringValue); |
| } |
| |
| bool isGeneralizedFunctionType(Token token) { |
| return optional('Function', token) && |
| (optional('<', token.next) || optional('(', token.next)); |
| } |
| |
| Token parseType(Token token) { |
| Token begin = token; |
| if (isGeneralizedFunctionType(token)) { |
| // A function type without return type. |
| // Push the non-existing return type first. The loop below will |
| // generate the full type. |
| listener.handleNoType(token); |
| } else if (identical(token.stringValue, 'void') && |
| isGeneralizedFunctionType(token.next)) { |
| listener.handleVoidKeyword(token); |
| token = token.next; |
| } else { |
| if (isValidTypeReference(token)) { |
| token = parseIdentifier(token, IdentifierContext.typeReference); |
| token = parseQualifiedRestOpt( |
| token, IdentifierContext.typeReferenceContinuation); |
| } else { |
| token = reportUnrecoverableError(token, ErrorKind.ExpectedType)?.next; |
| listener.handleInvalidTypeReference(token); |
| } |
| token = parseTypeArgumentsOpt(token); |
| listener.handleType(begin, token); |
| } |
| |
| // While we see a `Function(` treat the pushed type as return type. |
| // For example: `int Function() Function(int) Function(String x)`. |
| while (isGeneralizedFunctionType(token)) { |
| token = parseFunctionType(token); |
| } |
| return token; |
| } |
| |
| /// Parses a generalized function type. |
| /// |
| /// The return type must already be pushed. |
| Token parseFunctionType(Token token) { |
| assert(optional('Function', token)); |
| Token functionToken = token; |
| token = token.next; |
| token = parseTypeVariablesOpt(token); |
| token = parseFormalParameters(token, inFunctionType: true); |
| listener.handleFunctionType(functionToken, token); |
| return token; |
| } |
| |
| Token parseTypeArgumentsOpt(Token token) { |
| return parseStuff( |
| token, |
| (t) => listener.beginTypeArguments(t), |
| (t) => parseType(t), |
| (c, bt, et) => listener.endTypeArguments(c, bt, et), |
| (t) => listener.handleNoTypeArguments(t)); |
| } |
| |
| Token parseTypeVariablesOpt(Token token) { |
| return parseStuff( |
| token, |
| (t) => listener.beginTypeVariables(t), |
| (t) => parseTypeVariable(t), |
| (c, bt, et) => listener.endTypeVariables(c, bt, et), |
| (t) => listener.handleNoTypeVariables(t)); |
| } |
| |
| // TODO(ahe): Clean this up. |
| Token parseStuff(Token token, Function beginStuff, Function stuffParser, |
| Function endStuff, Function handleNoStuff) { |
| if (optional('<', token)) { |
| Token begin = token; |
| beginStuff(begin); |
| int count = 0; |
| do { |
| token = stuffParser(token.next); |
| ++count; |
| } while (optional(',', token)); |
| Token next = token.next; |
| if (identical(token.stringValue, '>>')) { |
| token = new SymbolToken(GT_INFO, token.charOffset); |
| token.next = new SymbolToken(GT_INFO, token.charOffset + 1); |
| token.next.next = next; |
| } |
| endStuff(count, begin, token); |
| return expect('>', token); |
| } |
| handleNoStuff(token); |
| return token; |
| } |
| |
| Token parseTopLevelMember(Token token) { |
| Token start = token; |
| listener.beginTopLevelMember(token); |
| |
| Link<Token> identifiers = findMemberName(token); |
| if (identifiers.isEmpty) { |
| return reportUnrecoverableError(start, ErrorKind.ExpectedDeclaration) |
| ?.next; |
| } |
| Token afterName = identifiers.head; |
| identifiers = identifiers.tail; |
| |
| if (identifiers.isEmpty) { |
| return reportUnrecoverableError(start, ErrorKind.ExpectedDeclaration) |
| ?.next; |
| } |
| Token name = identifiers.head; |
| identifiers = identifiers.tail; |
| Token getOrSet; |
| if (!identifiers.isEmpty) { |
| String value = identifiers.head.stringValue; |
| if ((identical(value, 'get')) || (identical(value, 'set'))) { |
| getOrSet = identifiers.head; |
| identifiers = identifiers.tail; |
| } |
| } |
| Token type; |
| if (!identifiers.isEmpty) { |
| if (isValidTypeReference(identifiers.head)) { |
| type = identifiers.head; |
| identifiers = identifiers.tail; |
| } |
| } |
| |
| token = afterName; |
| bool isField; |
| while (true) { |
| // Loop to allow the listener to rewrite the token stream for |
| // error handling. |
| final String value = token.stringValue; |
| if ((identical(value, '(')) || |
| (identical(value, '{')) || |
| (identical(value, '=>'))) { |
| isField = false; |
| break; |
| } else if ((identical(value, '=')) || (identical(value, ','))) { |
| isField = true; |
| break; |
| } else if (identical(value, ';')) { |
| if (getOrSet != null) { |
| // If we found a "get" keyword, this must be an abstract |
| // getter. |
| isField = (!identical(getOrSet.stringValue, 'get')); |
| // TODO(ahe): This feels like a hack. |
| } else { |
| isField = true; |
| } |
| break; |
| } else { |
| token = |
| reportUnrecoverableError(token, ErrorKind.UnexpectedToken)?.next; |
| if (identical(token.kind, EOF_TOKEN)) return token; |
| } |
| } |
| var modifiers = identifiers.reverse(); |
| return isField |
| ? parseFields(start, modifiers, type, getOrSet, name, true) |
| : parseTopLevelMethod(start, modifiers, type, getOrSet, name); |
| } |
| |
| bool isVarFinalOrConst(Token token) { |
| String value = token.stringValue; |
| return identical('var', value) || |
| identical('final', value) || |
| identical('const', value); |
| } |
| |
| Token expectVarFinalOrConst( |
| Link<Token> modifiers, bool hasType, bool allowStatic) { |
| int modifierCount = 0; |
| Token staticModifier; |
| if (allowStatic && |
| !modifiers.isEmpty && |
| optional('static', modifiers.head)) { |
| staticModifier = modifiers.head; |
| modifierCount++; |
| parseModifier(staticModifier); |
| modifiers = modifiers.tail; |
| } |
| if (modifiers.isEmpty) { |
| listener.handleModifiers(modifierCount); |
| return null; |
| } |
| if (modifiers.tail.isEmpty) { |
| Token modifier = modifiers.head; |
| if (isVarFinalOrConst(modifier)) { |
| modifierCount++; |
| parseModifier(modifier); |
| listener.handleModifiers(modifierCount); |
| // TODO(ahe): The caller checks for "var Type name", perhaps we should |
| // check here instead. |
| return modifier; |
| } |
| } |
| |
| // Slow case to report errors. |
| List<Token> modifierList = modifiers.toList(); |
| Token varFinalOrConst = |
| modifierList.firstWhere(isVarFinalOrConst, orElse: () => null); |
| if (allowStatic && staticModifier == null) { |
| staticModifier = modifierList.firstWhere( |
| (modifier) => optional('static', modifier), |
| orElse: () => null); |
| if (staticModifier != null) { |
| modifierCount++; |
| parseModifier(staticModifier); |
| modifierList.remove(staticModifier); |
| } |
| } |
| bool hasTypeOrModifier = hasType; |
| if (varFinalOrConst != null) { |
| parseModifier(varFinalOrConst); |
| modifierCount++; |
| hasTypeOrModifier = true; |
| modifierList.remove(varFinalOrConst); |
| } |
| listener.handleModifiers(modifierCount); |
| var kind = hasTypeOrModifier |
| ? ErrorKind.ExtraneousModifier |
| : ErrorKind.ExtraneousModifierReplace; |
| for (Token modifier in modifierList) { |
| reportRecoverableError(modifier, kind, {'modifier': modifier}); |
| } |
| return null; |
| } |
| |
| /// Removes the optional `covariant` token from the modifiers, if there |
| /// is no `static` in the list, and `covariant` is the first modifier. |
| Link<Token> removeOptCovariantTokenIfNotStatic(Link<Token> modifiers) { |
| if (modifiers.isEmpty || |
| !identical(modifiers.first.stringValue, 'covariant')) { |
| return modifiers; |
| } |
| for (Token modifier in modifiers.tail) { |
| if (identical(modifier.stringValue, 'static')) { |
| return modifiers; |
| } |
| } |
| return modifiers.tail; |
| } |
| |
| Token parseFields(Token start, Link<Token> modifiers, Token type, |
| Token getOrSet, Token name, bool isTopLevel) { |
| bool hasType = type != null; |
| |
| Token covariantKeyword; |
| if (getOrSet == null && !isTopLevel) { |
| // TODO(ahe): replace the method removeOptCovariantTokenIfNotStatic with |
| // a better mechanism. |
| Link<Token> newModifiers = removeOptCovariantTokenIfNotStatic(modifiers); |
| if (!identical(newModifiers, modifiers)) { |
| covariantKeyword = modifiers.first; |
| modifiers = newModifiers; |
| } |
| } |
| |
| Token varFinalOrConst = |
| expectVarFinalOrConst(modifiers, hasType, !isTopLevel); |
| bool isVar = false; |
| bool hasModifier = false; |
| if (varFinalOrConst != null) { |
| hasModifier = true; |
| isVar = optional('var', varFinalOrConst); |
| } |
| |
| if (getOrSet != null) { |
| var kind = (hasModifier || hasType) |
| ? ErrorKind.ExtraneousModifier |
| : ErrorKind.ExtraneousModifierReplace; |
| reportRecoverableError(getOrSet, kind, {'modifier': getOrSet}); |
| } |
| |
| if (!hasType) { |
| listener.handleNoType(name); |
| } else if (optional('void', type) && |
| !isGeneralizedFunctionType(type.next)) { |
| listener.handleNoType(name); |
| // TODO(ahe): This error is reported twice, second time is from |
| // [parseVariablesDeclarationMaybeSemicolon] via |
| // [PartialFieldListElement.parseNode]. |
| reportRecoverableError(type, ErrorKind.InvalidVoid); |
| } else { |
| parseType(type); |
| if (isVar) { |
| reportRecoverableError(modifiers.head, ErrorKind.ExtraneousModifier, |
| {'modifier': modifiers.head}); |
| } |
| } |
| |
| IdentifierContext context = isTopLevel |
| ? IdentifierContext.topLevelVariableDeclaration |
| : IdentifierContext.fieldDeclaration; |
| Token token = parseIdentifier(name, context); |
| |
| int fieldCount = 1; |
| token = parseFieldInitializerOpt(token); |
| while (optional(',', token)) { |
| token = parseIdentifier(token.next, context); |
| token = parseFieldInitializerOpt(token); |
| ++fieldCount; |
| } |
| Token semicolon = token; |
| token = expectSemicolon(token); |
| if (isTopLevel) { |
| listener.endTopLevelFields(fieldCount, start, semicolon); |
| } else { |
| listener.endFields(fieldCount, covariantKeyword, start, semicolon); |
| } |
| return token; |
| } |
| |
| Token parseTopLevelMethod(Token start, Link<Token> modifiers, Token type, |
| Token getOrSet, Token name) { |
| listener.beginTopLevelMethod(start, name); |
| Token externalModifier; |
| // TODO(johnniwinther): Move error reporting to resolution to give more |
| // specific error messages. |
| for (Token modifier in modifiers) { |
| if (externalModifier == null && optional('external', modifier)) { |
| externalModifier = modifier; |
| } else { |
| reportRecoverableError( |
| modifier, ErrorKind.ExtraneousModifier, {'modifier': modifier}); |
| } |
| } |
| if (externalModifier != null) { |
| parseModifier(externalModifier); |
| listener.handleModifiers(1); |
| } else { |
| listener.handleModifiers(0); |
| } |
| |
| if (type == null) { |
| listener.handleNoType(name); |
| } else { |
| parseReturnTypeOpt(type); |
| } |
| Token token = |
| parseIdentifier(name, IdentifierContext.topLevelFunctionDeclaration); |
| |
| if (getOrSet == null) { |
| token = parseTypeVariablesOpt(token); |
| } else { |
| listener.handleNoTypeVariables(token); |
| } |
| token = parseFormalParametersOpt(token); |
| bool previousAsyncAwaitKeywordsEnabled = asyncAwaitKeywordsEnabled; |
| token = parseAsyncModifier(token); |
| token = parseFunctionBody(token, false, externalModifier != null); |
| asyncAwaitKeywordsEnabled = previousAsyncAwaitKeywordsEnabled; |
| Token endToken = token; |
| token = token.next; |
| listener.endTopLevelMethod(start, getOrSet, endToken); |
| return token; |
| } |
| |
| /// Looks ahead to find the name of a member. Returns a link of the modifiers, |
| /// set/get, (operator) name, and either the start of the method body or the |
| /// end of the declaration. |
| /// |
| /// Examples: |
| /// |
| /// int get foo; |
| /// results in |
| /// [';', 'foo', 'get', 'int'] |
| /// |
| /// |
| /// static const List<int> foo = null; |
| /// results in |
| /// ['=', 'foo', 'List', 'const', 'static'] |
| /// |
| /// |
| /// get foo async* { return null } |
| /// results in |
| /// ['{', 'foo', 'get'] |
| /// |
| /// |
| /// operator *(arg) => null; |
| /// results in |
| /// ['(', '*', 'operator'] |
| /// |
| Link<Token> findMemberName(Token token) { |
| // TODO(ahe): This method is rather broken for examples like this: |
| // |
| // get<T>(){} |
| // |
| // In addition, the loop below will include things that can't be |
| // identifiers. This may be desirable (for error recovery), or |
| // not. Regardless, this method probably needs an overhaul. |
| Link<Token> identifiers = const Link<Token>(); |
| |
| // `true` if 'get' has been seen. |
| bool isGetter = false; |
| // `true` if an identifier has been seen after 'get'. |
| bool hasName = false; |
| |
| while (token.kind != EOF_TOKEN) { |
| if (optional('get', token)) { |
| isGetter = true; |
| } else if (hasName && |
| (optional("sync", token) || optional("async", token))) { |
| // Skip. |
| token = token.next; |
| if (optional("*", token)) { |
| // Skip. |
| token = token.next; |
| } |
| continue; |
| } else if (optional("(", token) || |
| optional("{", token) || |
| optional("=>", token)) { |
| // A method. |
| identifiers = identifiers.prepend(token); |
| return listener.handleMemberName(identifiers); |
| } else if (optional("=", token) || |
| optional(";", token) || |
| optional(",", token)) { |
| // A field or abstract getter. |
| identifiers = identifiers.prepend(token); |
| return listener.handleMemberName(identifiers); |
| } else if (isGetter) { |
| hasName = true; |
| } |
| identifiers = identifiers.prepend(token); |
| |
| if (!isGeneralizedFunctionType(token)) { |
| // Read a potential return type. |
| if (isValidTypeReference(token)) { |
| // type ... |
| if (optional('.', token.next)) { |
| // type '.' ... |
| if (token.next.next.isIdentifier()) { |
| // type '.' identifier |
| token = token.next.next; |
| } |
| } |
| if (optional('<', token.next)) { |
| if (token.next is BeginGroupToken) { |
| BeginGroupToken beginGroup = token.next; |
| if (beginGroup.endGroup == null) { |
| reportUnrecoverableError(beginGroup, ErrorKind.UnmatchedToken); |
| } else { |
| token = beginGroup.endGroup; |
| } |
| } |
| } |
| } |
| token = token.next; |
| } |
| while (isGeneralizedFunctionType(token)) { |
| token = token.next; |
| if (optional('<', token)) { |
| if (token is BeginGroupToken) { |
| BeginGroupToken beginGroup = token; |
| if (beginGroup.endGroup == null) { |
| reportUnrecoverableError(beginGroup, ErrorKind.UnmatchedToken); |
| } else { |
| token = beginGroup.endGroup.next; |
| } |
| } |
| } |
| if (!optional('(', token)) { |
| if (optional(';', token)) { |
| reportRecoverableError(token, ErrorKind.ExpectedOpenParens); |
| } |
| token = expect("(", token); |
| } |
| if (token is BeginGroupToken) { |
| BeginGroupToken beginGroup = token; |
| if (beginGroup.endGroup == null) { |
| reportUnrecoverableError(beginGroup, ErrorKind.UnmatchedToken); |
| } else { |
| token = beginGroup.endGroup.next; |
| } |
| } |
| } |
| } |
| return listener.handleMemberName(const Link<Token>()); |
| } |
| |
| Token parseFieldInitializerOpt(Token token) { |
| if (optional('=', token)) { |
| Token assignment = token; |
| listener.beginFieldInitializer(token); |
| token = parseExpression(token.next); |
| listener.endFieldInitializer(assignment); |
| } else { |
| listener.handleNoFieldInitializer(token); |
| } |
| return token; |
| } |
| |
| Token parseVariableInitializerOpt(Token token) { |
| if (optional('=', token)) { |
| Token assignment = token; |
| listener.beginVariableInitializer(token); |
| token = parseExpression(token.next); |
| listener.endVariableInitializer(assignment); |
| } else { |
| listener.handleNoVariableInitializer(token); |
| } |
| return token; |
| } |
| |
| Token parseInitializersOpt(Token token) { |
| if (optional(':', token)) { |
| return parseInitializers(token); |
| } else { |
| listener.handleNoInitializers(); |
| return token; |
| } |
| } |
| |
| Token parseInitializers(Token token) { |
| Token begin = token; |
| listener.beginInitializers(begin); |
| expect(':', token); |
| int count = 0; |
| bool old = mayParseFunctionExpressions; |
| mayParseFunctionExpressions = false; |
| do { |
| token = token.next; |
| listener.beginInitializer(token); |
| token = parseExpression(token); |
| listener.endInitializer(token); |
| ++count; |
| } while (optional(',', token)); |
| mayParseFunctionExpressions = old; |
| listener.endInitializers(count, begin, token); |
| return token; |
| } |
| |
| Token parseLiteralStringOrRecoverExpression(Token token) { |
| if (identical(token.kind, STRING_TOKEN)) { |
| return parseLiteralString(token); |
| } else { |
| reportRecoverableError(token, ErrorKind.ExpectedString); |
| return parseRecoverExpression(token); |
| } |
| } |
| |
| Token expectSemicolon(Token token) { |
| return expect(';', token); |
| } |
| |
| bool isModifier(Token token) { |
| final String value = token.stringValue; |
| return (identical('final', value)) || |
| (identical('var', value)) || |
| (identical('const', value)) || |
| (identical('abstract', value)) || |
| (identical('static', value)) || |
| (identical('external', value)); |
| } |
| |
| Token parseModifier(Token token) { |
| assert(isModifier(token)); |
| listener.handleModifier(token); |
| return token.next; |
| } |
| |
| void parseModifierList(Link<Token> tokens) { |
| int count = 0; |
| for (; !tokens.isEmpty; tokens = tokens.tail) { |
| Token token = tokens.head; |
| if (isModifier(token)) { |
| parseModifier(token); |
| } else { |
| reportUnrecoverableError(token, ErrorKind.UnexpectedToken); |
| // Skip the remaining modifiers. |
| break; |
| } |
| count++; |
| } |
| listener.handleModifiers(count); |
| } |
| |
| Token parseModifiers(Token token) { |
| // TODO(ahe): The calling convention of this method probably needs to |
| // change. For example, this is parsed as a local variable declaration: |
| // `abstract foo;`. Ideally, this example should be handled as a local |
| // variable having the type `abstract` (which should be reported as |
| // `ErrorKind.BuiltInIdentifierAsType` by [parseIdentifier]). |
| int count = 0; |
| while (identical(token.kind, KEYWORD_TOKEN)) { |
| if (!isModifier(token)) break; |
| token = parseModifier(token); |
| count++; |
| } |
| listener.handleModifiers(count); |
| return token; |
| } |
| |
| /** |
| * Returns the first token after the type starting at [token]. |
| * |
| * This method assumes that [token] is an identifier (or void). |
| * Use [peekAfterIfType] if [token] isn't known to be an identifier. |
| */ |
| Token peekAfterType(Token token) { |
| // We are looking at "identifier ...". |
| Token peek = token; |
| if (!isGeneralizedFunctionType(token)) { |
| peek = peekAfterNominalType(token); |
| } |
| |
| // We might have just skipped over the return value of the function type. |
| // Check again, if we are now at a function type position. |
| while (isGeneralizedFunctionType(peek)) { |
| peek = peekAfterFunctionType(peek.next); |
| } |
| return peek; |
| } |
| |
| /** |
| * Returns the first token after the nominal type starting at [token]. |
| * |
| * This method assumes that [token] is an identifier (or void). |
| */ |
| Token peekAfterNominalType(Token token) { |
| Token peek = token.next; |
| if (identical(peek.kind, PERIOD_TOKEN)) { |
| if (peek.next.isIdentifier()) { |
| // Look past a library prefix. |
| peek = peek.next.next; |
| } |
| } |
| // We are looking at "qualified ...". |
| if (identical(peek.kind, LT_TOKEN)) { |
| // Possibly generic type. |
| // We are looking at "qualified '<'". |
| BeginGroupToken beginGroupToken = peek; |
| Token gtToken = beginGroupToken.endGroup; |
| if (gtToken != null) { |
| // We are looking at "qualified '<' ... '>' ...". |
| peek = gtToken.next; |
| } |
| } |
| return peek; |
| } |
| |
| /** |
| * Returns the first token after the function type starting at [token]. |
| * |
| * The token must be at the token *after* the `Function` token position. That |
| * is, the return type and the `Function` token must have already been |
| * skipped. |
| * |
| * This function only skips over one function type syntax. |
| * If necessary, this function must be called multiple times. |
| * |
| * Example: |
| * ``` |
| * int Function() Function<T>(int) |
| * ^ ^ |
| * A call to this function must be either at `(` or at `<`. |
| * If `token` pointed to the first `(`, then the returned |
| * token points to the second `Function` token. |
| */ |
| Token peekAfterFunctionType(Token token) { |
| // Possible inputs are: |
| // ( ... ) |
| // < ... >( ... ) |
| |
| Token peek = token; |
| // If there is a generic argument to the function, skip over that one first. |
| if (identical(peek.kind, LT_TOKEN)) { |
| BeginGroupToken beginGroupToken = peek; |
| Token closeToken = beginGroupToken.endGroup; |
| if (closeToken != null) { |
| peek = closeToken.next; |
| } |
| } |
| |
| // Now we just need to skip over the formals. |
| expect('(', peek); |
| |
| BeginGroupToken beginGroupToken = peek; |
| Token closeToken = beginGroupToken.endGroup; |
| if (closeToken != null) { |
| peek = closeToken.next; |
| } |
| |
| return peek; |
| } |
| |
| /** |
| * If [token] is the start of a type, returns the token after that type. |
| * If [token] is not the start of a type, null is returned. |
| */ |
| Token peekAfterIfType(Token token) { |
| if (!optional('void', token) && !token.isIdentifier()) { |
| return null; |
| } |
| return peekAfterType(token); |
| } |
| |
| Token skipClassBody(Token token) { |
| if (!optional('{', token)) { |
| return reportUnrecoverableError(token, ErrorKind.ExpectedClassBodyToSkip) |
| ?.next; |
| } |
| BeginGroupToken beginGroupToken = token; |
| Token endGroup = beginGroupToken.endGroup; |
| if (endGroup == null) { |
| return reportUnrecoverableError(beginGroupToken, ErrorKind.UnmatchedToken) |
| ?.next; |
| } else if (!identical(endGroup.kind, $CLOSE_CURLY_BRACKET)) { |
| return reportUnrecoverableError(beginGroupToken, ErrorKind.UnmatchedToken) |
| ?.next; |
| } |
| return endGroup; |
| } |
| |
| Token parseClassBody(Token token) { |
| Token begin = token; |
| listener.beginClassBody(token); |
| if (!optional('{', token)) { |
| token = |
| reportUnrecoverableError(token, ErrorKind.ExpectedClassBody)?.next; |
| } |
| token = token.next; |
| int count = 0; |
| while (notEofOrValue('}', token)) { |
| token = parseMember(token); |
| ++count; |
| } |
| expect('}', token); |
| listener.endClassBody(count, begin, token); |
| return token; |
| } |
| |
| bool isGetOrSet(Token token) { |
| final String value = token.stringValue; |
| return (identical(value, 'get')) || (identical(value, 'set')); |
| } |
| |
| bool isFactoryDeclaration(Token token) { |
| if (optional('external', token)) token = token.next; |
| if (optional('const', token)) token = token.next; |
| return optional('factory', token); |
| } |
| |
| Token parseMember(Token token) { |
| token = parseMetadataStar(token); |
| Token start = token; |
| listener.beginMember(token); |
| if (isFactoryDeclaration(token)) { |
| token = parseFactoryMethod(token); |
| listener.endMember(); |
| assert(token != null); |
| return token; |
| } |
| |
| Link<Token> identifiers = findMemberName(token); |
| if (identifiers.isEmpty) { |
| return reportUnrecoverableError(start, ErrorKind.ExpectedDeclaration) |
| ?.next; |
| } |
| Token afterName = identifiers.head; |
| identifiers = identifiers.tail; |
| |
| if (identifiers.isEmpty) { |
| return reportUnrecoverableError(start, ErrorKind.ExpectedDeclaration) |
| ?.next; |
| } |
| Token name = identifiers.head; |
| identifiers = identifiers.tail; |
| if (!identifiers.isEmpty) { |
| if (optional('operator', identifiers.head)) { |
| name = identifiers.head; |
| identifiers = identifiers.tail; |
| } |
| } |
| Token getOrSet; |
| if (!identifiers.isEmpty) { |
| if (isGetOrSet(identifiers.head)) { |
| getOrSet = identifiers.head; |
| identifiers = identifiers.tail; |
| } |
| } |
| Token type; |
| if (!identifiers.isEmpty) { |
| if (isValidTypeReference(identifiers.head)) { |
| type = identifiers.head; |
| identifiers = identifiers.tail; |
| } |
| } |
| |
| token = afterName; |
| bool isField; |
| while (true) { |
| // Loop to allow the listener to rewrite the token stream for |
| // error handling. |
| final String value = token.stringValue; |
| if ((identical(value, '(')) || |
| (identical(value, '.')) || |
| (identical(value, '{')) || |
| (identical(value, '=>')) || |
| (identical(value, '<'))) { |
| isField = false; |
| break; |
| } else if (identical(value, ';')) { |
| if (getOrSet != null) { |
| // If we found a "get" keyword, this must be an abstract |
| // getter. |
| isField = !optional("get", getOrSet); |
| // TODO(ahe): This feels like a hack. |
| } else { |
| isField = true; |
| } |
| break; |
| } else if ((identical(value, '=')) || (identical(value, ','))) { |
| isField = true; |
| break; |
| } else { |
| token = |
| reportUnrecoverableError(token, ErrorKind.UnexpectedToken)?.next; |
| if (identical(token.kind, EOF_TOKEN)) { |
| // TODO(ahe): This is a hack, see parseTopLevelMember. |
| listener.endFields(1, null, start, token); |
| listener.endMember(); |
| return token; |
| } |
| } |
| } |
| |
| var modifiers = identifiers.reverse(); |
| token = isField |
| ? parseFields(start, modifiers, type, getOrSet, name, false) |
| : parseMethod(start, modifiers, type, getOrSet, name); |
| listener.endMember(); |
| return token; |
| } |
| |
| Token parseMethod(Token start, Link<Token> modifiers, Token type, |
| Token getOrSet, Token name) { |
| listener.beginMethod(start, name); |
| Token externalModifier; |
| Token staticModifier; |
| Token constModifier; |
| int modifierCount = 0; |
| int allowedModifierCount = 1; |
| // TODO(johnniwinther): Move error reporting to resolution to give more |
| // specific error messages. |
| for (Token modifier in modifiers) { |
| if (externalModifier == null && optional('external', modifier)) { |
| modifierCount++; |
| externalModifier = modifier; |
| if (modifierCount != allowedModifierCount) { |
| reportRecoverableError( |
| modifier, ErrorKind.ExtraneousModifier, {'modifier': modifier}); |
| } |
| allowedModifierCount++; |
| } else if (staticModifier == null && optional('static', modifier)) { |
| modifierCount++; |
| staticModifier = modifier; |
| if (modifierCount != allowedModifierCount) { |
| reportRecoverableError( |
| modifier, ErrorKind.ExtraneousModifier, {'modifier': modifier}); |
| } |
| } else if (constModifier == null && optional('const', modifier)) { |
| modifierCount++; |
| constModifier = modifier; |
| if (modifierCount != allowedModifierCount) { |
| reportRecoverableError( |
| modifier, ErrorKind.ExtraneousModifier, {'modifier': modifier}); |
| } |
| } else { |
| reportRecoverableError( |
| modifier, ErrorKind.ExtraneousModifier, {'modifier': modifier}); |
| } |
| } |
| if (getOrSet != null && constModifier != null) { |
| reportRecoverableError(constModifier, ErrorKind.ExtraneousModifier, |
| {'modifier': constModifier}); |
| } |
| parseModifierList(modifiers); |
| |
| if (type == null) { |
| listener.handleNoType(name); |
| } else { |
| parseReturnTypeOpt(type); |
| } |
| Token token; |
| if (optional('operator', name)) { |
| token = parseOperatorName(name); |
| if (staticModifier != null) { |
| reportRecoverableError(staticModifier, ErrorKind.ExtraneousModifier, |
| {'modifier': staticModifier}); |
| } |
| } else { |
| token = parseIdentifier(name, IdentifierContext.methodDeclaration); |
| } |
| |
| token = parseQualifiedRestOpt( |
| token, IdentifierContext.methodDeclarationContinuation); |
| if (getOrSet == null) { |
| token = parseTypeVariablesOpt(token); |
| } else { |
| listener.handleNoTypeVariables(token); |
| } |
| token = parseFormalParametersOpt(token); |
| token = parseInitializersOpt(token); |
| bool previousAsyncAwaitKeywordsEnabled = asyncAwaitKeywordsEnabled; |
| token = parseAsyncModifier(token); |
| if (optional('=', token)) { |
| token = parseRedirectingFactoryBody(token); |
| } else { |
| token = parseFunctionBody( |
| token, false, staticModifier == null || externalModifier != null); |
| } |
| asyncAwaitKeywordsEnabled = previousAsyncAwaitKeywordsEnabled; |
| listener.endMethod(getOrSet, start, token); |
| return token.next; |
| } |
| |
| Token parseFactoryMethod(Token token) { |
| assert(isFactoryDeclaration(token)); |
| Token start = token; |
| bool isExternal = false; |
| int modifierCount = 0; |
| while (isModifier(token)) { |
| if (optional('external', token)) { |
| isExternal = true; |
| } |
| token = parseModifier(token); |
| modifierCount++; |
| } |
| listener.handleModifiers(modifierCount); |
| Token factoryKeyword = token; |
| listener.beginFactoryMethod(factoryKeyword); |
| token = expect('factory', token); |
| token = parseConstructorReference(token); |
| token = parseFormalParameters(token); |
| token = parseAsyncModifier(token); |
| if (optional('=', token)) { |
| token = parseRedirectingFactoryBody(token); |
| } else { |
| token = parseFunctionBody(token, false, isExternal); |
| } |
| listener.endFactoryMethod(start, factoryKeyword, token); |
| return token.next; |
| } |
| |
| Token parseOperatorName(Token token) { |
| assert(optional('operator', token)); |
| if (isUserDefinableOperator(token.next.stringValue)) { |
| Token operator = token; |
| token = token.next; |
| listener.handleOperatorName(operator, token); |
| return token.next; |
| } else { |
| return parseIdentifier(token, IdentifierContext.operatorName); |
| } |
| } |
| |
| Token parseFunction(Token token, Token getOrSet) { |
| listener.beginFunction(token); |
| token = parseModifiers(token); |
| if (identical(getOrSet, token)) { |
| // get <name> => ... |
| token = token.next; |
| listener.handleNoType(token); |
| listener.beginFunctionName(token); |
| if (optional('operator', token)) { |
| token = parseOperatorName(token); |
| } else { |
| token = |
| parseIdentifier(token, IdentifierContext.localAccessorDeclaration); |
| } |
| } else if (optional('operator', token)) { |
| // operator <op> (... |
| listener.handleNoType(token); |
| listener.beginFunctionName(token); |
| token = parseOperatorName(token); |
| } else { |
| // <type>? <get>? <name> |
| token = parseReturnTypeOpt(token); |
| if (identical(getOrSet, token)) { |
| token = token.next; |
| } |
| listener.beginFunctionName(token); |
| if (optional('operator', token)) { |
| token = parseOperatorName(token); |
| } else { |
| token = |
| parseIdentifier(token, IdentifierContext.localFunctionDeclaration); |
| } |
| } |
| token = parseQualifiedRestOpt( |
| token, IdentifierContext.localFunctionDeclarationContinuation); |
| listener.endFunctionName(token); |
| if (getOrSet == null) { |
| token = parseTypeVariablesOpt(token); |
| } else { |
| listener.handleNoTypeVariables(token); |
| } |
| token = parseFormalParametersOpt(token); |
| token = parseInitializersOpt(token); |
| bool previousAsyncAwaitKeywordsEnabled = asyncAwaitKeywordsEnabled; |
| token = parseAsyncModifier(token); |
| token = parseFunctionBody(token, false, true); |
| asyncAwaitKeywordsEnabled = previousAsyncAwaitKeywordsEnabled; |
| listener.endFunction(getOrSet, token); |
| return token.next; |
| } |
| |
| Token parseUnnamedFunction(Token token) { |
| listener.beginUnnamedFunction(token); |
| token = parseFormalParameters(token); |
| bool previousAsyncAwaitKeywordsEnabled = asyncAwaitKeywordsEnabled; |
| token = parseAsyncModifier(token); |
| bool isBlock = optional('{', token); |
| token = parseFunctionBody(token, true, false); |
| asyncAwaitKeywordsEnabled = previousAsyncAwaitKeywordsEnabled; |
| listener.endUnnamedFunction(token); |
| return isBlock ? token.next : token; |
| } |
| |
| Token parseFunctionDeclaration(Token token) { |
| listener.beginFunctionDeclaration(token); |
| token = parseFunction(token, null); |
| listener.endFunctionDeclaration(token); |
| return token; |
| } |
| |
| Token parseFunctionExpression(Token token) { |
| listener.beginFunction(token); |
| listener.handleModifiers(0); |
| token = parseReturnTypeOpt(token); |
| listener.beginFunctionName(token); |
| token = parseIdentifier(token, IdentifierContext.functionExpressionName); |
| listener.endFunctionName(token); |
| token = parseTypeVariablesOpt(token); |
| token = parseFormalParameters(token); |
| listener.handleNoInitializers(); |
| bool previousAsyncAwaitKeywordsEnabled = asyncAwaitKeywordsEnabled; |
| token = parseAsyncModifier(token); |
| bool isBlock = optional('{', token); |
| token = parseFunctionBody(token, true, false); |
| asyncAwaitKeywordsEnabled = previousAsyncAwaitKeywordsEnabled; |
| listener.endFunction(null, token); |
| return isBlock ? token.next : token; |
| } |
| |
| Token parseConstructorReference(Token token) { |
| Token start = token; |
| listener.beginConstructorReference(start); |
| token = parseIdentifier(token, IdentifierContext.constructorReference); |
| token = parseQualifiedRestOpt( |
| token, IdentifierContext.constructorReferenceContinuation); |
| token = parseTypeArgumentsOpt(token); |
| Token period = null; |
| if (optional('.', token)) { |
| period = token; |
| token = parseIdentifier(token.next, |
| IdentifierContext.constructorReferenceContinuationAfterTypeArguments); |
| } else { |
| listener |
| .handleNoConstructorReferenceContinuationAfterTypeArguments(token); |
| } |
| listener.endConstructorReference(start, period, token); |
| return token; |
| } |
| |
| Token parseRedirectingFactoryBody(Token token) { |
| listener.beginRedirectingFactoryBody(token); |
| assert(optional('=', token)); |
| Token equals = token; |
| token = parseConstructorReference(token.next); |
| Token semicolon = token; |
| expectSemicolon(token); |
| listener.endRedirectingFactoryBody(equals, semicolon); |
| return token; |
| } |
| |
| Token skipFunctionBody(Token token, bool isExpression, bool allowAbstract) { |
| assert(!isExpression); |
| token = skipAsyncModifier(token); |
| String value = token.stringValue; |
| if (identical(value, ';')) { |
| if (!allowAbstract) { |
| reportRecoverableError(token, ErrorKind.ExpectedBody); |
| } |
| listener.handleNoFunctionBody(token); |
| } else { |
| if (identical(value, '=>')) { |
| token = parseExpression(token.next); |
| expectSemicolon(token); |
| listener.handleFunctionBodySkipped(token, true); |
| } else if (identical(value, '=')) { |
| reportRecoverableError(token, ErrorKind.ExpectedBody); |
| token = parseExpression(token.next); |
| expectSemicolon(token); |
| listener.handleFunctionBodySkipped(token, true); |
| } else { |
| token = skipBlock(token); |
| listener.handleFunctionBodySkipped(token, false); |
| } |
| } |
| return token; |
| } |
| |
| Token parseFunctionBody(Token token, bool isExpression, bool allowAbstract) { |
| if (optional(';', token)) { |
| if (!allowAbstract) { |
| reportRecoverableError(token, ErrorKind.ExpectedBody); |
| } |
| listener.handleEmptyFunctionBody(token); |
| return token; |
| } else if (optional('=>', token)) { |
| Token begin = token; |
| token = parseExpression(token.next); |
| if (!isExpression) { |
| expectSemicolon(token); |
| listener.handleExpressionFunctionBody(begin, token); |
| } else { |
| listener.handleExpressionFunctionBody(begin, null); |
| } |
| return token; |
| } else if (optional('=', token)) { |
| Token begin = token; |
| // Recover from a bad factory method. |
| reportRecoverableError(token, ErrorKind.ExpectedBody); |
| token = parseExpression(token.next); |
| if (!isExpression) { |
| expectSemicolon(token); |
| listener.handleExpressionFunctionBody(begin, token); |
| } else { |
| listener.handleExpressionFunctionBody(begin, null); |
| } |
| return token; |
| } |
| Token begin = token; |
| int statementCount = 0; |
| if (!optional('{', token)) { |
| token = |
| reportUnrecoverableError(token, ErrorKind.ExpectedFunctionBody)?.next; |
| listener.handleInvalidFunctionBody(token); |
| return token; |
| } |
| |
| listener.beginBlockFunctionBody(begin); |
| token = token.next; |
| while (notEofOrValue('}', token)) { |
| token = parseStatement(token); |
| ++statementCount; |
| } |
| listener.endBlockFunctionBody(statementCount, begin, token); |
| expect('}', token); |
| return token; |
| } |
| |
| Token skipAsyncModifier(Token token) { |
| String value = token.stringValue; |
| if (identical(value, 'async')) { |
| token = token.next; |
| value = token.stringValue; |
| |
| if (identical(value, '*')) { |
| token = token.next; |
| } |
| } else if (identical(value, 'sync')) { |
| token = token.next; |
| value = token.stringValue; |
| |
| if (identical(value, '*')) { |
| token = token.next; |
| } |
| } |
| return token; |
| } |
| |
| Token parseAsyncModifier(Token token) { |
| Token async; |
| Token star; |
| asyncAwaitKeywordsEnabled = false; |
| if (optional('async', token)) { |
| asyncAwaitKeywordsEnabled = true; |
| async = token; |
| token = token.next; |
| if (optional('*', token)) { |
| star = token; |
| token = token.next; |
| } |
| } else if (optional('sync', token)) { |
| async = token; |
| token = token.next; |
| if (optional('*', token)) { |
| asyncAwaitKeywordsEnabled = true; |
| star = token; |
| token = token.next; |
| } else { |
| reportRecoverableError(async, ErrorKind.InvalidSyncModifier); |
| } |
| } |
| listener.handleAsyncModifier(async, star); |
| return token; |
| } |
| |
| int statementDepth = 0; |
| Token parseStatement(Token token) { |
| if (statementDepth++ > 500) { |
| // This happens for degenerate programs, for example, a lot of nested |
| // if-statements. The language test deep_nesting2_negative_test, for |
| // example, provokes this. |
| return reportUnrecoverableError(token, ErrorKind.StackOverflow)?.next; |
| } |
| Token result = parseStatementX(token); |
| statementDepth--; |
| return result; |
| } |
| |
| Token parseStatementX(Token token) { |
| final value = token.stringValue; |
| if (identical(token.kind, IDENTIFIER_TOKEN)) { |
| return parseExpressionStatementOrDeclaration(token); |
| } else if (identical(value, '{')) { |
| return parseBlock(token); |
| } else if (identical(value, 'return')) { |
| return parseReturnStatement(token); |
| } else if (identical(value, 'var') || identical(value, 'final')) { |
| return parseVariablesDeclaration(token); |
| } else if (identical(value, 'if')) { |
| return parseIfStatement(token); |
| } else if (asyncAwaitKeywordsEnabled && identical(value, 'await')) { |
| if (identical(token.next.stringValue, 'for')) { |
| return parseForStatement(token, token.next); |
| } else { |
| return parseExpressionStatement(token); |
| } |
| } else if (identical(value, 'for')) { |
| return parseForStatement(null, token); |
| } else if (identical(value, 'rethrow')) { |
| return parseRethrowStatement(token); |
| } else if (identical(value, 'throw') && optional(';', token.next)) { |
| // TODO(kasperl): Stop dealing with throw here. |
| return parseRethrowStatement(token); |
| } else if (identical(value, 'void')) { |
| return parseExpressionStatementOrDeclaration(token); |
| } else if (identical(value, 'while')) { |
| return parseWhileStatement(token); |
| } else if (identical(value, 'do')) { |
| return parseDoWhileStatement(token); |
| } else if (identical(value, 'try')) { |
| return parseTryStatement(token); |
| } else if (identical(value, 'switch')) { |
| return parseSwitchStatement(token); |
| } else if (identical(value, 'break')) { |
| return parseBreakStatement(token); |
| } else if (identical(value, 'continue')) { |
| return parseContinueStatement(token); |
| } else if (identical(value, 'assert')) { |
| return parseAssertStatement(token); |
| } else if (identical(value, ';')) { |
| return parseEmptyStatement(token); |
| } else if (asyncAwaitKeywordsEnabled && identical(value, 'yield')) { |
| return parseYieldStatement(token); |
| } else if (identical(value, 'const')) { |
| return parseExpressionStatementOrConstDeclaration(token); |
| } else if (token.isIdentifier()) { |
| return parseExpressionStatementOrDeclaration(token); |
| } else { |
| return parseExpressionStatement(token); |
| } |
| } |
| |
| Token parseYieldStatement(Token token) { |
| Token begin = token; |
| listener.beginYieldStatement(begin); |
| assert(identical('yield', token.stringValue)); |
| token = token.next; |
| Token starToken; |
| if (optional('*', token)) { |
| starToken = token; |
| token = token.next; |
| } |
| token = parseExpression(token); |
| listener.endYieldStatement(begin, starToken, token); |
| return expectSemicolon(token); |
| } |
| |
| Token parseReturnStatement(Token token) { |
| Token begin = token; |
| listener.beginReturnStatement(begin); |
| assert(identical('return', token.stringValue)); |
| token = token.next; |
| if (optional(';', token)) { |
| listener.endReturnStatement(false, begin, token); |
| } else { |
| token = parseExpression(token); |
| listener.endReturnStatement(true, begin, token); |
| } |
| return expectSemicolon(token); |
| } |
| |
| Token peekIdentifierAfterType(Token token) { |
| Token peek = peekAfterType(token); |
| if (peek != null && peek.isIdentifier()) { |
| // We are looking at "type identifier". |
| return peek; |
| } else { |
| return null; |
| } |
| } |
| |
| Token peekIdentifierAfterOptionalType(Token token) { |
| Token peek = peekAfterIfType(token); |
| if (peek != null && peek.isIdentifier()) { |
| // We are looking at "type identifier". |
| return peek; |
| } else if (token.isIdentifier()) { |
| // We are looking at "identifier". |
| return token; |
| } else { |
| return null; |
| } |
| } |
| |
| Token parseExpressionStatementOrDeclaration(Token token) { |
| assert(token.isIdentifier() || identical(token.stringValue, 'void')); |
| Token identifier = peekIdentifierAfterType(token); |
| if (identifier != null) { |
| assert(identifier.isIdentifier()); |
| Token afterId = identifier.next; |
| int afterIdKind = afterId.kind; |
| if (identical(afterIdKind, EQ_TOKEN) || |
| identical(afterIdKind, SEMICOLON_TOKEN) || |
| identical(afterIdKind, COMMA_TOKEN)) { |
| // We are looking at "type identifier" followed by '=', ';', ','. |
| return parseVariablesDeclaration(token); |
| } else if (identical(afterIdKind, OPEN_PAREN_TOKEN)) { |
| // We are looking at "type identifier '('". |
| BeginGroupToken beginParen = afterId; |
| Token endParen = beginParen.endGroup; |
| // TODO(eernst): Check for NPE as described in issue 26252. |
| Token afterParens = endParen.next; |
| if (optional('{', afterParens) || |
| optional('=>', afterParens) || |
| optional('async', afterParens) || |
| optional('sync', afterParens)) { |
| // We are looking at "type identifier '(' ... ')'" followed |
| // by '{', '=>', 'async', or 'sync'. |
| return parseFunctionDeclaration(token); |
| } |
| } else if (identical(afterIdKind, LT_TOKEN)) { |
| // We are looking at "type identifier '<'". |
| BeginGroupToken beginAngle = afterId; |
| Token endAngle = beginAngle.endGroup; |
| if (endAngle != null && |
| identical(endAngle.next.kind, OPEN_PAREN_TOKEN)) { |
| BeginGroupToken beginParen = endAngle.next; |
| Token endParen = beginParen.endGroup; |
| if (endParen != null) { |
| Token afterParens = endParen.next; |
| if (optional('{', afterParens) || |
| optional('=>', afterParens) || |
| optional('async', afterParens) || |
| optional('sync', afterParens)) { |
| // We are looking at "type identifier '<' ... '>' '(' ... ')'" |
| // followed by '{', '=>', 'async', or 'sync'. |
| return parseFunctionDeclaration(token); |
| } |
| } |
| } |
| } |
| // Fall-through to expression statement. |
| } else { |
| if (optional(':', token.next)) { |
| return parseLabeledStatement(token); |
| } else if (optional('(', token.next)) { |
| BeginGroupToken begin = token.next; |
| // TODO(eernst): Check for NPE as described in issue 26252. |
| String afterParens = begin.endGroup.next.stringValue; |
| if (identical(afterParens, '{') || |
| identical(afterParens, '=>') || |
| identical(afterParens, 'async') || |
| identical(afterParens, 'sync')) { |
| return parseFunctionDeclaration(token); |
| } |
| } else if (optional('<', token.next)) { |
| BeginGroupToken beginAngle = token.next; |
| Token endAngle = beginAngle.endGroup; |
| if (endAngle != null && |
| identical(endAngle.next.kind, OPEN_PAREN_TOKEN)) { |
| BeginGroupToken beginParen = endAngle.next; |
| Token endParen = beginParen.endGroup; |
| if (endParen != null) { |
| String afterParens = endParen.next.stringValue; |
| if (identical(afterParens, '{') || |
| identical(afterParens, '=>') || |
| identical(afterParens, 'async') || |
| identical(afterParens, 'sync')) { |
| return parseFunctionDeclaration(token); |
| } |
| } |
| } |
| // Fall through to expression statement. |
| } |
| } |
| return parseExpressionStatement(token); |
| } |
| |
| Token parseExpressionStatementOrConstDeclaration(Token token) { |
| assert(identical(token.stringValue, 'const')); |
| if (isModifier(token.next)) { |
| return parseVariablesDeclaration(token); |
| } |
| Token identifier = peekIdentifierAfterOptionalType(token.next); |
| if (identifier != null) { |
| assert(identifier.isIdentifier()); |
| Token afterId = identifier.next; |
| int afterIdKind = afterId.kind; |
| if (identical(afterIdKind, EQ_TOKEN) || |
| identical(afterIdKind, SEMICOLON_TOKEN) || |
| identical(afterIdKind, COMMA_TOKEN)) { |
| // We are looking at "const type identifier" followed by '=', ';', or |
| // ','. |
| return parseVariablesDeclaration(token); |
| } |
| // Fall-through to expression statement. |
| } |
| |
| return parseExpressionStatement(token); |
| } |
| |
| Token parseLabel(Token token) { |
| token = parseIdentifier(token, IdentifierContext.labelDeclaration); |
| Token colon = token; |
| token = expect(':', token); |
| listener.handleLabel(colon); |
| return token; |
| } |
| |
| Token parseLabeledStatement(Token token) { |
| int labelCount = 0; |
| do { |
| token = parseLabel(token); |
| labelCount++; |
| } while (token.isIdentifier() && optional(':', token.next)); |
| listener.beginLabeledStatement(token, labelCount); |
| token = parseStatement(token); |
| listener.endLabeledStatement(labelCount); |
| return token; |
| } |
| |
| Token parseExpressionStatement(Token token) { |
| listener.beginExpressionStatement(token); |
| token = parseExpression(token); |
| listener.endExpressionStatement(token); |
| return expectSemicolon(token); |
| } |
| |
| Token skipExpression(Token token) { |
| while (true) { |
| final kind = token.kind; |
| final value = token.stringValue; |
| if ((identical(kind, EOF_TOKEN)) || |
| (identical(value, ';')) || |
| (identical(value, ',')) || |
| (identical(value, '}')) || |
| (identical(value, ')')) || |
| (identical(value, ']'))) { |
| break; |
| } |
| if (identical(value, '=') || |
| identical(value, '?') || |
| identical(value, ':') || |
| identical(value, '??')) { |
| var nextValue = token.next.stringValue; |
| if (identical(nextValue, 'const')) { |
| token = token.next; |
| nextValue = token.next.stringValue; |
| } |
| if (identical(nextValue, '{')) { |
| // Handle cases like this: |
| // class Foo { |
| // var map; |
| // Foo() : map = {}; |
| // Foo.x() : map = true ? {} : {}; |
| // } |
| BeginGroupToken begin = token.next; |
| token = (begin.endGroup != null) ? begin.endGroup : token; |
| token = token.next; |
| continue; |
| } |
| if (identical(nextValue, '<')) { |
| // Handle cases like this: |
| // class Foo { |
| // var map; |
| // Foo() : map = <String, Foo>{}; |
| // Foo.x() : map = true ? <String, Foo>{} : <String, Foo>{}; |
| // } |
| BeginGroupToken begin = token.next; |
| token = (begin.endGroup != null) ? begin.endGroup : token; |
| token = token.next; |
| if (identical(token.stringValue, '{')) { |
| begin = token; |
| token = (begin.endGroup != null) ? begin.endGroup : token; |
| token = token.next; |
| } |
| continue; |
| } |
| } |
| if (!mayParseFunctionExpressions && identical(value, '{')) { |
| break; |
| } |
| if (token is BeginGroupToken) { |
| BeginGroupToken begin = token; |
| token = (begin.endGroup != null) ? begin.endGroup : token; |
| } else if (token is ErrorToken) { |
| reportErrorToken(token, false)?.next; |
| } |
| token = token.next; |
| } |
| return token; |
| } |
| |
| Token parseRecoverExpression(Token token) => parseExpression(token); |
| |
| int expressionDepth = 0; |
| Token parseExpression(Token token) { |
| if (expressionDepth++ > 500) { |
| // This happens in degenerate programs, for example, with a lot of nested |
| // list literals. This is provoked by, for examaple, the language test |
| // deep_nesting1_negative_test. |
| return reportUnrecoverableError(token, ErrorKind.StackOverflow)?.next; |
| } |
| listener.beginExpression(token); |
| Token result = optional('throw', token) |
| ? parseThrowExpression(token, true) |
| : parsePrecedenceExpression(token, ASSIGNMENT_PRECEDENCE, true); |
| expressionDepth--; |
| return result; |
| } |
| |
| Token parseExpressionWithoutCascade(Token token) { |
| listener.beginExpression(token); |
| return optional('throw', token) |
| ? parseThrowExpression(token, false) |
| : parsePrecedenceExpression(token, ASSIGNMENT_PRECEDENCE, false); |
| } |
| |
| Token parseConditionalExpressionRest(Token token) { |
| assert(optional('?', token)); |
| Token question = token; |
| token = parseExpressionWithoutCascade(token.next); |
| Token colon = token; |
| token = expect(':', token); |
| token = parseExpressionWithoutCascade(token); |
| listener.handleConditionalExpression(question, colon); |
| return token; |
| } |
| |
| Token parsePrecedenceExpression( |
| Token token, int precedence, bool allowCascades) { |
| assert(precedence >= 1); |
| assert(precedence <= POSTFIX_PRECEDENCE); |
| token = parseUnaryExpression(token, allowCascades); |
| PrecedenceInfo info = token.info; |
| int tokenLevel = info.precedence; |
| for (int level = tokenLevel; level >= precedence; --level) { |
| while (identical(tokenLevel, level)) { |
| Token operator = token; |
| if (identical(tokenLevel, CASCADE_PRECEDENCE)) { |
| if (!allowCascades) { |
| return token; |
| } |
| token = parseCascadeExpression(token); |
| } else if (identical(tokenLevel, ASSIGNMENT_PRECEDENCE)) { |
| // Right associative, so we recurse at the same precedence |
| // level. |
| listener.beginExpression(token.next); |
| token = parsePrecedenceExpression(token.next, level, allowCascades); |
| listener.handleAssignmentExpression(operator); |
| } else if (identical(tokenLevel, POSTFIX_PRECEDENCE)) { |
| if (identical(info, PERIOD_INFO) || |
| identical(info, QUESTION_PERIOD_INFO)) { |
| // Left associative, so we recurse at the next higher precedence |
| // level. However, POSTFIX_PRECEDENCE is the highest level, so we |
| // should just call [parseUnaryExpression] directly. However, a |
| // unary expression isn't legal after a period, so we call |
| // [parsePrimary] instead. |
| token = parsePrimary(token.next); |
| listener.handleBinaryExpression(operator); |
| } else if ((identical(info, OPEN_PAREN_INFO)) || |
| (identical(info, OPEN_SQUARE_BRACKET_INFO))) { |
| token = parseArgumentOrIndexStar(token); |
| } else if ((identical(info, PLUS_PLUS_INFO)) || |
| (identical(info, MINUS_MINUS_INFO))) { |
| listener.handleUnaryPostfixAssignmentExpression(token); |
| token = token.next; |
| } else { |
| token = reportUnrecoverableError(token, ErrorKind.UnexpectedToken) |
| ?.next; |
| } |
| } else if (identical(info, IS_INFO)) { |
| token = parseIsOperatorRest(token); |
| } else if (identical(info, AS_INFO)) { |
| token = parseAsOperatorRest(token); |
| } else if (identical(info, QUESTION_INFO)) { |
| token = parseConditionalExpressionRest(token); |
| } else { |
| // Left associative, so we recurse at the next higher |
| // precedence level. |
| listener.beginExpression(token.next); |
| token = |
| parsePrecedenceExpression(token.next, level + 1, allowCascades); |
| listener.handleBinaryExpression(operator); |
| } |
| info = token.info; |
| tokenLevel = info.precedence; |
| if (level == EQUALITY_PRECEDENCE || level == RELATIONAL_PRECEDENCE) { |
| // We don't allow (a == b == c) or (a < b < c). |
| // Continue the outer loop if we have matched one equality or |
| // relational operator. |
| break; |
| } |
| } |
| } |
| return token; |
| } |
| |
| Token parseCascadeExpression(Token token) { |
| listener.beginCascade(token); |
| assert(optional('..', token)); |
| Token cascadeOperator = token; |
| token = token.next; |
| if (optional('[', token)) { |
| token = parseArgumentOrIndexStar(token); |
| } else if (token.isIdentifier()) { |
| token = parseSend(token, IdentifierContext.expressionContinuation); |
| listener.handleBinaryExpression(cascadeOperator); |
| } else { |
| return reportUnrecoverableError(token, ErrorKind.UnexpectedToken)?.next; |
| } |
| Token mark; |
| do { |
| mark = token; |
| if (optional('.', token)) { |
| Token period = token; |
| token = parseSend(token.next, IdentifierContext.expressionContinuation); |
| listener.handleBinaryExpression(period); |
| } |
| token = parseArgumentOrIndexStar(token); |
| } while (!identical(mark, token)); |
| |
| if (identical(token.info.precedence, ASSIGNMENT_PRECEDENCE)) { |
| Token assignment = token; |
| token = parseExpressionWithoutCascade(token.next); |
| listener.handleAssignmentExpression(assignment); |
| } |
| listener.endCascade(); |
| return token; |
| } |
| |
| Token parseUnaryExpression(Token token, bool allowCascades) { |
| String value = token.stringValue; |
| // Prefix: |
| if (asyncAwaitKeywordsEnabled && optional('await', token)) { |
| return parseAwaitExpression(token, allowCascades); |
| } else if (identical(value, '+')) { |
| // Dart no longer allows prefix-plus. |
| reportRecoverableError(token, ErrorKind.UnsupportedPrefixPlus); |
| return parseUnaryExpression(token.next, allowCascades); |
| } else if ((identical(value, '!')) || |
| (identical(value, '-')) || |
| (identical(value, '~'))) { |
| Token operator = token; |
| // Right associative, so we recurse at the same precedence |
| // level. |
| token = parsePrecedenceExpression( |
| token.next, POSTFIX_PRECEDENCE, allowCascades); |
| listener.handleUnaryPrefixExpression(operator); |
| } else if ((identical(value, '++')) || identical(value, '--')) { |
| // TODO(ahe): Validate this is used correctly. |
| Token operator = token; |
| // Right associative, so we recurse at the same precedence |
| // level. |
| token = parsePrecedenceExpression( |
| token.next, POSTFIX_PRECEDENCE, allowCascades); |
| listener.handleUnaryPrefixAssignmentExpression(operator); |
| } else { |
| token = parsePrimary(token); |
| } |
| return token; |
| } |
| |
| Token parseArgumentOrIndexStar(Token token) { |
| Token beginToken = token; |
| while (true) { |
| if (optional('[', token)) { |
| Token openSquareBracket = token; |
| bool old = mayParseFunctionExpressions; |
| mayParseFunctionExpressions = true; |
| token = parseExpression(token.next); |
| mayParseFunctionExpressions = old; |
| listener.handleIndexedExpression(openSquareBracket, token); |
| token = expect(']', token); |
| } else if (optional('(', token)) { |
| listener.handleNoTypeArguments(token); |
| token = parseArguments(token); |
| listener.endSend(beginToken, token); |
| } else { |
| break; |
| } |
| } |
| return token; |
| } |
| |
| Token parsePrimary(Token token) { |
| final kind = token.kind; |
| if (kind == IDENTIFIER_TOKEN) { |
| return parseSendOrFunctionLiteral(token); |
| } else if (kind == INT_TOKEN || kind == HEXADECIMAL_TOKEN) { |
| return parseLiteralInt(token); |
| } else if (kind == DOUBLE_TOKEN) { |
| return parseLiteralDouble(token); |
| } else if (kind == STRING_TOKEN) { |
| return parseLiteralString(token); |
| } else if (kind == HASH_TOKEN) { |
| return parseLiteralSymbol(token); |
| } else if (kind == KEYWORD_TOKEN) { |
| final value = token.stringValue; |
| if (value == 'true' || value == 'false') { |
| return parseLiteralBool(token); |
| } else if (value == 'null') { |
| return parseLiteralNull(token); |
| } else if (value == 'this') { |
| return parseThisExpression(token); |
| } else if (value == 'super') { |
| return parseSuperExpression(token); |
| } else if (value == 'new') { |
| return parseNewExpression(token); |
| } else if (value == 'const') { |
| return parseConstExpression(token); |
| } else if (value == 'void') { |
| return parseFunctionExpression(token); |
| } else if (asyncAwaitKeywordsEnabled && |
| (value == 'yield' || value == 'async')) { |
| return expressionExpected(token); |
| } else if (token.isIdentifier()) { |
| return parseSendOrFunctionLiteral(token); |
| } else { |
| return expressionExpected(token); |
| } |
| } else if (kind == OPEN_PAREN_TOKEN) { |
| return parseParenthesizedExpressionOrFunctionLiteral(token); |
| } else if (kind == OPEN_SQUARE_BRACKET_TOKEN || token.stringValue == '[]') { |
| listener.handleNoTypeArguments(token); |
| return parseLiteralListSuffix(token, null); |
| } else if (kind == OPEN_CURLY_BRACKET_TOKEN) { |
| listener.handleNoTypeArguments(token); |
| return parseLiteralMapSuffix(token, null); |
| } else if (kind == LT_TOKEN) { |
| return parseLiteralListOrMapOrFunction(token, null); |
| } else { |
| return expressionExpected(token); |
| } |
| } |
| |
| Token expressionExpected(Token token) { |
| token = reportUnrecoverableError(token, ErrorKind.ExpectedExpression)?.next; |
| listener.handleInvalidExpression(token); |
| return token; |
| } |
| |
| Token parseParenthesizedExpressionOrFunctionLiteral(Token token) { |
| BeginGroupToken beginGroup = token; |
| // TODO(eernst): Check for NPE as described in issue 26252. |
| Token nextToken = beginGroup.endGroup.next; |
| int kind = nextToken.kind; |
| if (mayParseFunctionExpressions && |
| (identical(kind, FUNCTION_TOKEN) || |
| identical(kind, OPEN_CURLY_BRACKET_TOKEN) || |
| (identical(kind, KEYWORD_TOKEN) && |
| (nextToken.lexeme == 'async' || nextToken.lexeme == 'sync')))) { |
| listener.handleNoTypeVariables(token); |
| return parseUnnamedFunction(token); |
| } else { |
| bool old = mayParseFunctionExpressions; |
| mayParseFunctionExpressions = true; |
| token = parseParenthesizedExpression(token); |
| mayParseFunctionExpressions = old; |
| return token; |
| } |
| } |
| |
| Token parseParenthesizedExpression(Token token) { |
| // We expect [begin] to be of type [BeginGroupToken], but we don't know for |
| // sure until after calling expect. |
| dynamic begin = token; |
| token = expect('(', token); |
| // [begin] is now known to have type [BeginGroupToken]. |
| token = parseExpression(token); |
| if (!identical(begin.endGroup, token)) { |
| reportUnrecoverableError(token, ErrorKind.UnexpectedToken)?.next; |
| token = begin.endGroup; |
| } |
| listener.handleParenthesizedExpression(begin); |
| return expect(')', token); |
| } |
| |
| Token parseThisExpression(Token token) { |
| Token beginToken = token; |
| listener.handleThisExpression(token); |
| token = token.next; |
| if (optional('(', token)) { |
| // Constructor forwarding. |
| listener.handleNoTypeArguments(token); |
| token = parseArguments(token); |
| listener.endSend(beginToken, token); |
| } |
| return token; |
| } |
| |
| Token parseSuperExpression(Token token) { |
| Token beginToken = token; |
| listener.handleSuperExpression(token); |
| token = token.next; |
| if (optional('(', token)) { |
| // Super constructor. |
| listener.handleNoTypeArguments(token); |
| token = parseArguments(token); |
| listener.endSend(beginToken, token); |
| } |
| return token; |
| } |
| |
| /// '[' (expressionList ','?)? ']'. |
| /// |
| /// Provide [constKeyword] if preceded by 'const', null if not. |
| /// This is a suffix parser because it is assumed that type arguments have |
| /// been parsed, or `listener.handleNoTypeArguments(..)` has been executed. |
| Token parseLiteralListSuffix(Token token, Token constKeyword) { |
| assert(optional('[', token) || optional('[]', token)); |
| Token beginToken = token; |
| int count = 0; |
| if (optional('[', token)) { |
| bool old = mayParseFunctionExpressions; |
| mayParseFunctionExpressions = true; |
| do { |
| if (optional(']', token.next)) { |
| token = token.next; |
| break; |
| } |
| token = parseExpression(token.next); |
| ++count; |
| } while (optional(',', token)); |
| mayParseFunctionExpressions = old; |
| listener.handleLiteralList(count, beginToken, constKeyword, token); |
| return expect(']', token); |
| } |
| // Looking at '[]'. |
| listener.handleLiteralList(0, token, constKeyword, token); |
| return token.next; |
| } |
| |
| /// '{' (mapLiteralEntry (',' mapLiteralEntry)* ','?)? '}'. |
| /// |
| /// Provide token for [constKeyword] if preceded by 'const', null if not. |
| /// This is a suffix parser because it is assumed that type arguments have |
| /// been parsed, or `listener.handleNoTypeArguments(..)` has been executed. |
| Token parseLiteralMapSuffix(Token token, Token constKeyword) { |
| assert(optional('{', token)); |
| Token beginToken = token; |
| int count = 0; |
| bool old = mayParseFunctionExpressions; |
| mayParseFunctionExpressions = true; |
| do { |
| if (optional('}', token.next)) { |
| token = token.next; |
| break; |
| } |
| token = parseMapLiteralEntry(token.next); |
| ++count; |
| } while (optional(',', token)); |
| mayParseFunctionExpressions = old; |
| listener.handleLiteralMap(count, beginToken, constKeyword, token); |
| return expect('}', token); |
| } |
| |
| /// formalParameterList functionBody. |
| /// |
| /// This is a suffix parser because it is assumed that type arguments have |
| /// been parsed, or `listener.handleNoTypeArguments(..)` has been executed. |
| Token parseLiteralFunctionSuffix(Token token) { |
| assert(optional('(', token)); |
| BeginGroupToken beginGroup = token; |
| if (beginGroup.endGroup != null) { |
| Token nextToken = beginGroup.endGroup.next; |
| int kind = nextToken.kind; |
| if (identical(kind, FUNCTION_TOKEN) || |
| identical(kind, OPEN_CURLY_BRACKET_TOKEN) || |
| (identical(kind, KEYWORD_TOKEN) && |
| (nextToken.lexeme == 'async' || nextToken.lexeme == 'sync'))) { |
| return parseUnnamedFunction(token); |
| } |
| // Fall through. |
| } |
| reportUnrecoverableError(token, ErrorKind.UnexpectedToken); |
| return null; |
| } |
| |
| /// genericListLiteral | genericMapLiteral | genericFunctionLiteral. |
| /// |
| /// Where |
| /// genericListLiteral ::= typeArguments '[' (expressionList ','?)? ']' |
| /// genericMapLiteral ::= |
| /// typeArguments '{' (mapLiteralEntry (',' mapLiteralEntry)* ','?)? '}' |
| /// genericFunctionLiteral ::= |
| /// typeParameters formalParameterList functionBody |
| /// Provide token for [constKeyword] if preceded by 'const', null if not. |
| Token parseLiteralListOrMapOrFunction(Token token, Token constKeyword) { |
| assert(optional('<', token)); |
| BeginGroupToken begin = token; |
| if (constKeyword == null && |
| begin.endGroup != null && |
| identical(begin.endGroup.next.kind, OPEN_PAREN_TOKEN)) { |
| token = parseTypeVariablesOpt(token); |
| return parseLiteralFunctionSuffix(token); |
| } else { |
| token = parseTypeArgumentsOpt(token); |
| if (optional('{', token)) { |
| return parseLiteralMapSuffix(token, constKeyword); |
| } else if ((optional('[', token)) || (optional('[]', token))) { |
| return parseLiteralListSuffix(token, constKeyword); |
| } |
| reportUnrecoverableError(token, ErrorKind.UnexpectedToken); |
| return null; |
| } |
| } |
| |
| Token parseMapLiteralEntry(Token token) { |
| listener.beginLiteralMapEntry(token); |
| // Assume the listener rejects non-string keys. |
| token = parseExpression(token); |
| Token colon = token; |
| token = expect(':', token); |
| token = parseExpression(token); |
| listener.endLiteralMapEntry(colon, token); |
| return token; |
| } |
| |
| Token parseSendOrFunctionLiteral(Token token) { |
| if (!mayParseFunctionExpressions) { |
| return parseSend(token, IdentifierContext.expression); |
| } |
| Token peek = peekAfterIfType(token); |
| if (peek != null && |
| identical(peek.kind, IDENTIFIER_TOKEN) && |
| isFunctionDeclaration(peek.next)) { |
| return parseFunctionExpression(token); |
| } else if (isFunctionDeclaration(token.next)) { |
| return parseFunctionExpression(token); |
| } else { |
| return parseSend(token, IdentifierContext.expression); |
| } |
| } |
| |
| bool isFunctionDeclaration(Token token) { |
| if (optional('<', token)) { |
| BeginGroupToken begin = token; |
| if (begin.endGroup == null) return false; |
| token = begin.endGroup.next; |
| } |
| if (optional('(', token)) { |
| BeginGroupToken begin = token; |
| // TODO(eernst): Check for NPE as described in issue 26252. |
| String afterParens = begin.endGroup.next.stringValue; |
| if (identical(afterParens, '{') || |
| identical(afterParens, '=>') || |
| identical(afterParens, 'async') || |
| identical(afterParens, 'sync')) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| Token parseRequiredArguments(Token token) { |
| if (optional('(', token)) { |
| token = parseArguments(token); |
| } else { |
| listener.handleNoArguments(token); |
| token = reportUnrecoverableError(token, ErrorKind.UnexpectedToken)?.next; |
| } |
| return token; |
| } |
| |
| Token parseNewExpression(Token token) { |
| Token newKeyword = token; |
| token = expect('new', token); |
| token = parseConstructorReference(token); |
| token = parseRequiredArguments(token); |
| listener.handleNewExpression(newKeyword); |
| return token; |
| } |
| |
| Token parseConstExpression(Token token) { |
| Token constKeyword = token; |
| token = expect('const', token); |
| final String value = token.stringValue; |
| if ((identical(value, '[')) || (identical(value, '[]'))) { |
| listener.handleNoTypeArguments(token); |
| return parseLiteralListSuffix(token, constKeyword); |
| } |
| if (identical(value, '{')) { |
| listener.handleNoTypeArguments(token); |
| return parseLiteralMapSuffix(token, constKeyword); |
| } |
| if (identical(value, '<')) { |
| return parseLiteralListOrMapOrFunction(token, constKeyword); |
| } |
| token = parseConstructorReference(token); |
| token = parseRequiredArguments(token); |
| listener.handleConstExpression(constKeyword); |
| return token; |
| } |
| |
| Token parseLiteralInt(Token token) { |
| listener.handleLiteralInt(token); |
| return token.next; |
| } |
| |
| Token parseLiteralDouble(Token token) { |
| listener.handleLiteralDouble(token); |
| return token.next; |
| } |
| |
| Token parseLiteralString(Token token) { |
| bool old = mayParseFunctionExpressions; |
| mayParseFunctionExpressions = true; |
| token = parseSingleLiteralString(token); |
| int count = 1; |
| while (identical(token.kind, STRING_TOKEN)) { |
| token = parseSingleLiteralString(token); |
| count++; |
| } |
| if (count > 1) { |
| listener.handleStringJuxtaposition(count); |
| } |
| mayParseFunctionExpressions = old; |
| return token; |
| } |
| |
| Token parseLiteralSymbol(Token token) { |
| Token hashToken = token; |
| listener.beginLiteralSymbol(hashToken); |
| token = token.next; |
| if (isUserDefinableOperator(token.stringValue)) { |
| listener.handleOperator(token); |
| listener.endLiteralSymbol(hashToken, 1); |
| return token.next; |
| } else if (identical(token.stringValue, 'void')) { |
| listener.handleSymbolVoid(token); |
| listener.endLiteralSymbol(hashToken, 1); |
| return token.next; |
| } else { |
| int count = 1; |
| token = parseIdentifier(token, IdentifierContext.literalSymbol); |
| while (identical(token.stringValue, '.')) { |
| count++; |
| token = parseIdentifier( |
| token.next, IdentifierContext.literalSymbolContinuation); |
| } |
| listener.endLiteralSymbol(hashToken, count); |
| return token; |
| } |
| } |
| |
| /** |
| * Only called when [:token.kind === STRING_TOKEN:]. |
| */ |
| Token parseSingleLiteralString(Token token) { |
| listener.beginLiteralString(token); |
| // Parsing the prefix, for instance 'x of 'x${id}y${id}z' |
| token = token.next; |
| int interpolationCount = 0; |
| var kind = token.kind; |
| while (kind != EOF_TOKEN) { |
| if (identical(kind, STRING_INTERPOLATION_TOKEN)) { |
| // Parsing ${expression}. |
| token = token.next; |
| token = parseExpression(token); |
| token = expect('}', token); |
| } else if (identical(kind, STRING_INTERPOLATION_IDENTIFIER_TOKEN)) { |
| // Parsing $identifier. |
| token = token.next; |
| token = parseExpression(token); |
| } else { |
| break; |
| } |
| ++interpolationCount; |
| // Parsing the infix/suffix, for instance y and z' of 'x${id}y${id}z' |
| token = parseStringPart(token); |
| kind = token.kind; |
| } |
| listener.endLiteralString(interpolationCount, token); |
| return token; |
| } |
| |
| Token parseLiteralBool(Token token) { |
| listener.handleLiteralBool(token); |
| return token.next; |
| } |
| |
| Token parseLiteralNull(Token token) { |
| listener.handleLiteralNull(token); |
| return token.next; |
| } |
| |
| Token parseSend(Token token, IdentifierContext context) { |
| Token beginToken = token; |
| listener.beginSend(token); |
| token = parseIdentifier(token, context); |
| if (isValidMethodTypeArguments(token)) { |
| token = parseTypeArgumentsOpt(token); |
| } else { |
| listener.handleNoTypeArguments(token); |
| } |
| token = parseArgumentsOpt(token); |
| listener.endSend(beginToken, token); |
| return token; |
| } |
| |
| Token skipArgumentsOpt(Token token) { |
| listener.handleNoArguments(token); |
| if (optional('(', token)) { |
| BeginGroupToken begin = token; |
| return begin.endGroup.next; |
| } else { |
| return token; |
| } |
| } |
| |
| Token parseArgumentsOpt(Token token) { |
| if (!optional('(', token)) { |
| listener.handleNoArguments(token); |
| return token; |
| } else { |
| return parseArguments(token); |
| } |
| } |
| |
| Token parseArguments(Token token) { |
| Token begin = token; |
| listener.beginArguments(begin); |
| assert(identical('(', token.stringValue)); |
| int argumentCount = 0; |
| if (optional(')', token.next)) { |
| listener.endArguments(argumentCount, begin, token.next); |
| return token.next.next; |
| } |
| bool old = mayParseFunctionExpressions; |
| mayParseFunctionExpressions = true; |
| do { |
| if (optional(')', token.next)) { |
| token = token.next; |
| break; |
| } |
| Token colon = null; |
| if (optional(':', token.next.next)) { |
| token = parseIdentifier( |
| token.next, IdentifierContext.namedArgumentReference); |
| colon = token; |
| } |
| token = parseExpression(token.next); |
| if (colon != null) listener.handleNamedArgument(colon); |
| ++argumentCount; |
| } while (optional(',', token)); |
| mayParseFunctionExpressions = old; |
| listener.endArguments(argumentCount, begin, token); |
| return expect(')', token); |
| } |
| |
| Token parseIsOperatorRest(Token token) { |
| assert(optional('is', token)); |
| Token operator = token; |
| Token not = null; |
| if (optional('!', token.next)) { |
| token = token.next; |
| not = token; |
| } |
| token = parseType(token.next); |
| listener.handleIsOperator(operator, not, token); |
| String value = token.stringValue; |
| if (identical(value, 'is') || identical(value, 'as')) { |
| // The is- and as-operators cannot be chained, but they can take part of |
| // expressions like: foo is Foo || foo is Bar. |
| reportUnrecoverableError(token, ErrorKind.UnexpectedToken); |
| } |
| return token; |
| } |
| |
| Token parseAsOperatorRest(Token token) { |
| assert(optional('as', token)); |
| Token operator = token; |
| token = parseType(token.next); |
| listener.handleAsOperator(operator, token); |
| String value = token.stringValue; |
| if (identical(value, 'is') || identical(value, 'as')) { |
| // The is- and as-operators cannot be chained. |
| reportUnrecoverableError(token, ErrorKind.UnexpectedToken); |
| } |
| return token; |
| } |
| |
| Token parseVariablesDeclaration(Token token) { |
| return parseVariablesDeclarationMaybeSemicolon(token, true); |
| } |
| |
| Token parseVariablesDeclarationNoSemicolon(Token token) { |
| // Only called when parsing a for loop, so this is for parsing locals. |
| return parseVariablesDeclarationMaybeSemicolon(token, false); |
| } |
| |
| Token parseVariablesDeclarationMaybeSemicolon( |
| Token token, bool endWithSemicolon) { |
| int count = 1; |
| listener.beginVariablesDeclaration(token); |
| token = parseModifiers(token); |
| token = parseTypeOpt(token); |
| token = parseOptionallyInitializedIdentifier(token); |
| while (optional(',', token)) { |
| token = parseOptionallyInitializedIdentifier(token.next); |
| ++count; |
| } |
| if (endWithSemicolon) { |
| Token semicolon = token; |
| token = expectSemicolon(semicolon); |
| listener.endVariablesDeclaration(count, semicolon); |
| return token; |
| } else { |
| listener.endVariablesDeclaration(count, null); |
| return token; |
| } |
| } |
| |
| Token parseOptionallyInitializedIdentifier(Token token) { |
| Token nameToken = token; |
| listener.beginInitializedIdentifier(token); |
| token = parseIdentifier(token, IdentifierContext.localVariableDeclaration); |
| token = parseVariableInitializerOpt(token); |
| listener.endInitializedIdentifier(nameToken); |
| return token; |
| } |
| |
| Token parseIfStatement(Token token) { |
| Token ifToken = token; |
| listener.beginIfStatement(ifToken); |
| token = expect('if', token); |
| token = parseParenthesizedExpression(token); |
| listener.beginThenStatement(token); |
| token = parseStatement(token); |
| listener.endThenStatement(token); |
| Token elseToken = null; |
| if (optional('else', token)) { |
| elseToken = token; |
| listener.beginElseStatement(token); |
| token = parseStatement(token.next); |
| listener.endElseStatement(token); |
| } |
| listener.endIfStatement(ifToken, elseToken); |
| return token; |
| } |
| |
| Token parseForStatement(Token awaitToken, Token token) { |
| Token forKeyword = token; |
| listener.beginForStatement(forKeyword); |
| token = expect('for', token); |
| Token leftParenthesis = token; |
| token = expect('(', token); |
| token = parseVariablesDeclarationOrExpressionOpt(token); |
| if (optional('in', token)) { |
| return parseForInRest(awaitToken, forKeyword, leftParenthesis, token); |
| } else { |
| if (awaitToken != null) { |
| reportRecoverableError(awaitToken, ErrorKind.InvalidAwaitFor); |
| } |
| return parseForRest(forKeyword, leftParenthesis, token); |
| } |
| } |
| |
| Token parseVariablesDeclarationOrExpressionOpt(Token token) { |
| final String value = token.stringValue; |
| if (identical(value, ';')) { |
| listener.handleNoExpression(token); |
| return token; |
| } else if (isOneOf3(token, 'var', 'final', 'const')) { |
| return parseVariablesDeclarationNoSemicolon(token); |
| } |
| Token identifier = peekIdentifierAfterType(token); |
| if (identifier != null) { |
| assert(identifier.isIdentifier()); |
| if (isOneOf4(identifier.next, '=', ';', ',', 'in')) { |
| return parseVariablesDeclarationNoSemicolon(token); |
| } |
| } |
| return parseExpression(token); |
| } |
| |
| Token parseForRest(Token forToken, Token leftParenthesis, Token token) { |
| Token leftSeparator = token; |
| token = expectSemicolon(token); |
| if (optional(';', token)) { |
| token = parseEmptyStatement(token); |
| } else { |
| token = parseExpressionStatement(token); |
| } |
| int expressionCount = 0; |
| while (true) { |
| if (optional(')', token)) break; |
| token = parseExpression(token); |
| ++expressionCount; |
| if (optional(',', token)) { |
| token = token.next; |
| } else { |
| break; |
| } |
| } |
| token = expect(')', token); |
| listener.beginForStatementBody(token); |
| token = parseStatement(token); |
| listener.endForStatementBody(token); |
| listener.endForStatement(forToken, leftSeparator, expressionCount, token); |
| return token; |
| } |
| |
| Token parseForInRest( |
| Token awaitToken, Token forKeyword, Token leftParenthesis, Token token) { |
| assert(optional('in', token)); |
| Token inKeyword = token; |
| token = token.next; |
| listener.beginForInExpression(token); |
| token = parseExpression(token); |
| listener.endForInExpression(token); |
| Token rightParenthesis = token; |
| token = expect(')', token); |
| listener.beginForInBody(token); |
| token = parseStatement(token); |
| listener.endForInBody(token); |
| listener.endForIn(awaitToken, forKeyword, leftParenthesis, inKeyword, |
| rightParenthesis, token); |
| return token; |
| } |
| |
| Token parseWhileStatement(Token token) { |
| Token whileToken = token; |
| listener.beginWhileStatement(whileToken); |
| token = expect('while', token); |
| token = parseParenthesizedExpression(token); |
| listener.beginWhileStatementBody(token); |
| token = parseStatement(token); |
| listener.endWhileStatementBody(token); |
| listener.endWhileStatement(whileToken, token); |
| return token; |
| } |
| |
| Token parseDoWhileStatement(Token token) { |
| Token doToken = token; |
| listener.beginDoWhileStatement(doToken); |
| token = expect('do', token); |
| listener.beginDoWhileStatementBody(token); |
| token = parseStatement(token); |
| listener.endDoWhileStatementBody(token); |
| Token whileToken = token; |
| token = expect('while', token); |
| token = parseParenthesizedExpression(token); |
| listener.endDoWhileStatement(doToken, whileToken, token); |
| return expectSemicolon(token); |
| } |
| |
| Token parseBlock(Token token) { |
| Token begin = token; |
| listener.beginBlock(begin); |
| int statementCount = 0; |
| token = expect('{', token); |
| while (notEofOrValue('}', token)) { |
| token = parseStatement(token); |
| ++statementCount; |
| } |
| listener.endBlock(statementCount, begin, token); |
| return expect('}', token); |
| } |
| |
| Token parseAwaitExpression(Token token, bool allowCascades) { |
| Token awaitToken = token; |
| listener.beginAwaitExpression(awaitToken); |
| token = expect('await', token); |
| token = parsePrecedenceExpression(token, POSTFIX_PRECEDENCE, allowCascades); |
| listener.endAwaitExpression(awaitToken, token); |
| return token; |
| } |
| |
| Token parseThrowExpression(Token token, bool allowCascades) { |
| Token throwToken = token; |
| listener.beginThrowExpression(throwToken); |
| token = expect('throw', token); |
| token = allowCascades |
| ? parseExpression(token) |
| : parseExpressionWithoutCascade(token); |
| listener.endThrowExpression(throwToken, token); |
| return token; |
| } |
| |
| Token parseRethrowStatement(Token token) { |
| Token throwToken = token; |
| listener.beginRethrowStatement(throwToken); |
| // TODO(kasperl): Disallow throw here. |
| if (identical(throwToken.stringValue, 'throw')) { |
| token = expect('throw', token); |
| } else { |
| token = expect('rethrow', token); |
| } |
| listener.endRethrowStatement(throwToken, token); |
| return expectSemicolon(token); |
| } |
| |
| Token parseTryStatement(Token token) { |
| assert(optional('try', token)); |
| Token tryKeyword = token; |
| listener.beginTryStatement(tryKeyword); |
| token = parseBlock(token.next); |
| int catchCount = 0; |
| |
| String value = token.stringValue; |
| while (identical(value, 'catch') || identical(value, 'on')) { |
| listener.beginCatchClause(token); |
| var onKeyword = null; |
| if (identical(value, 'on')) { |
| // on qualified catchPart? |
| onKeyword = token; |
| token = parseType(token.next); |
| value = token.stringValue; |
| } |
| Token catchKeyword = null; |
| if (identical(value, 'catch')) { |
| catchKeyword = token; |
| // TODO(ahe): Validate the "parameters". |
| token = parseFormalParameters(token.next); |
| } |
| listener.endCatchClause(token); |
| token = parseBlock(token); |
| ++catchCount; |
| listener.handleCatchBlock(onKeyword, catchKeyword); |
| value = token.stringValue; // while condition |
| } |
| |
| Token finallyKeyword = null; |
| if (optional('finally', token)) { |
| finallyKeyword = token; |
| token = parseBlock(token.next); |
| listener.handleFinallyBlock(finallyKeyword); |
| } |
| listener.endTryStatement(catchCount, tryKeyword, finallyKeyword); |
| return token; |
| } |
| |
| Token parseSwitchStatement(Token token) { |
| assert(optional('switch', token)); |
| Token switchKeyword = token; |
| listener.beginSwitchStatement(switchKeyword); |
| token = parseParenthesizedExpression(token.next); |
| token = parseSwitchBlock(token); |
| listener.endSwitchStatement(switchKeyword, token); |
| return token.next; |
| } |
| |
| Token parseSwitchBlock(Token token) { |
| Token begin = token; |
| listener.beginSwitchBlock(begin); |
| token = expect('{', token); |
| int caseCount = 0; |
| while (!identical(token.kind, EOF_TOKEN)) { |
| if (optional('}', token)) { |
| break; |
| } |
| token = parseSwitchCase(token); |
| ++caseCount; |
| } |
| listener.endSwitchBlock(caseCount, begin, token); |
| expect('}', token); |
| return token; |
| } |
| |
| /** |
| * Peek after the following labels (if any). The following token |
| * is used to determine if the labels belong to a statement or a |
| * switch case. |
| */ |
| Token peekPastLabels(Token token) { |
| while (token.isIdentifier() && optional(':', token.next)) { |
| token = token.next.next; |
| } |
| return token; |
| } |
| |
| /** |
| * Parse a group of labels, cases and possibly a default keyword and |
| * the statements that they select. |
| */ |
| Token parseSwitchCase(Token token) { |
| Token begin = token; |
| Token defaultKeyword = null; |
| int expressionCount = 0; |
| int labelCount = 0; |
| Token peek = peekPastLabels(token); |
| while (true) { |
| // Loop until we find something that can't be part of a switch case. |
| String value = peek.stringValue; |
| if (identical(value, 'default')) { |
| while (!identical(token, peek)) { |
| token = parseLabel(token); |
| labelCount++; |
| } |
| defaultKeyword = token; |
| token = expect(':', token.next); |
| peek = token; |
| break; |
| } else if (identical(value, 'case')) { |
| while (!identical(token, peek)) { |
| token = parseLabel(token); |
| labelCount++; |
| } |
| Token caseKeyword = token; |
| token = parseExpression(token.next); |
| Token colonToken = token; |
| token = expect(':', token); |
| listener.handleCaseMatch(caseKeyword, colonToken); |
| expressionCount++; |
| peek = peekPastLabels(token); |
| } else { |
| if (expressionCount == 0) { |
| // TODO(ahe): This is probably easy to recover from. |
| reportUnrecoverableError( |
| token, ErrorKind.ExpectedButGot, {"expected": "case"}); |
| } |
| break; |
| } |
| } |
| listener.beginSwitchCase(labelCount, expressionCount, begin); |
| // Finally zero or more statements. |
| int statementCount = 0; |
| while (!identical(token.kind, EOF_TOKEN)) { |
| String value = peek.stringValue; |
| if ((identical(value, 'case')) || |
| (identical(value, 'default')) || |
| ((identical(value, '}')) && (identical(token, peek)))) { |
| // A label just before "}" will be handled as a statement error. |
| break; |
| } else { |
| token = parseStatement(token); |
| } |
| statementCount++; |
| peek = peekPastLabels(token); |
| } |
| listener.handleSwitchCase(labelCount, expressionCount, defaultKeyword, |
| statementCount, begin, token); |
| return token; |
| } |
| |
| Token parseBreakStatement(Token token) { |
| assert(optional('break', token)); |
| Token breakKeyword = token; |
| token = token.next; |
| bool hasTarget = false; |
| if (token.isIdentifier()) { |
| token = parseIdentifier(token, IdentifierContext.labelReference); |
| hasTarget = true; |
| } |
| listener.handleBreakStatement(hasTarget, breakKeyword, token); |
| return expectSemicolon(token); |
| } |
| |
| Token parseAssertStatement(Token token) { |
| Token assertKeyword = token; |
| Token commaToken = null; |
| token = expect('assert', token); |
| Token leftParenthesis = token; |
| token = expect('(', token); |
| bool old = mayParseFunctionExpressions; |
| mayParseFunctionExpressions = true; |
| token = parseExpression(token); |
| if (optional(',', token)) { |
| commaToken = token; |
| token = token.next; |
| token = parseExpression(token); |
| } |
| Token rightParenthesis = token; |
| token = expect(')', token); |
| mayParseFunctionExpressions = old; |
| listener.handleAssertStatement( |
| assertKeyword, leftParenthesis, commaToken, rightParenthesis, token); |
| return expectSemicolon(token); |
| } |
| |
| Token parseContinueStatement(Token token) { |
| assert(optional('continue', token)); |
| Token continueKeyword = token; |
| token = token.next; |
| bool hasTarget = false; |
| if (token.isIdentifier()) { |
| token = parseIdentifier(token, IdentifierContext.labelReference); |
| hasTarget = true; |
| } |
| listener.handleContinueStatement(hasTarget, continueKeyword, token); |
| return expectSemicolon(token); |
| } |
| |
| Token parseEmptyStatement(Token token) { |
| listener.handleEmptyStatement(token); |
| return expectSemicolon(token); |
| } |
| |
| /// Don't call this method. Should only be used as a last resort when there |
| /// is no feasible way to recover from a parser error. |
| Token reportUnrecoverableError(Token token, ErrorKind kind, [Map arguments]) { |
| Token next; |
| if (token is ErrorToken) { |
| next = reportErrorToken(token, false); |
| } else { |
| arguments ??= {}; |
| arguments.putIfAbsent("actual", () => token.lexeme); |
| next = listener.handleUnrecoverableError(token, kind, arguments); |
| } |
| return next ?? skipToEof(token); |
| } |
| |
| void reportRecoverableError(Token token, ErrorKind kind, [Map arguments]) { |
| if (token is ErrorToken) { |
| reportErrorToken(token, true); |
| } else { |
| arguments ??= {}; |
| listener.handleRecoverableError(token, kind, arguments); |
| } |
| } |
| |
| Token reportErrorToken(ErrorToken token, bool isRecoverable) { |
| ErrorKind kind = token.errorCode; |
| Map arguments = const {}; |
| switch (kind) { |
| case ErrorKind.AsciiControlCharacter: |
| case ErrorKind.NonAsciiIdentifier: |
| case ErrorKind.NonAsciiWhitespace: |
| case ErrorKind.Encoding: |
| String hex = token.character.toRadixString(16); |
| if (hex.length < 4) { |
| String padding = "0000".substring(hex.length); |
| hex = "$padding$hex"; |
| } |
| arguments = {'characterHex': hex}; |
| break; |
| |
| case ErrorKind.UnterminatedString: |
| arguments = {'quote': token.start}; |
| break; |
| |
| case ErrorKind.UnmatchedToken: |
| String begin = token.begin.lexeme; |
| String end = closeBraceFor(begin); |
| arguments = {'begin': begin, 'end': end}; |
| break; |
| |
| case ErrorKind.Unspecified: |
| arguments = {"text": token.assertionMessage}; |
| break; |
| |
| default: |
| break; |
| } |
| if (isRecoverable) { |
| listener.handleRecoverableError(token, kind, arguments); |
| return null; |
| } else { |
| return listener.handleUnrecoverableError(token, kind, arguments); |
| } |
| } |
| } |