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