blob: 0f60a714892dd784b39970862aa0d4def748b722 [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.google.dart.compiler.parser;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.CharStreams;
import com.google.dart.compiler.DartCompilationError;
import com.google.dart.compiler.DartCompilerListener;
import com.google.dart.compiler.DartSource;
import com.google.dart.compiler.ErrorCode;
import com.google.dart.compiler.ErrorSeverity;
import com.google.dart.compiler.InternalCompilerException;
import com.google.dart.compiler.LibrarySource;
import com.google.dart.compiler.PackageLibraryManager;
import com.google.dart.compiler.Source;
import com.google.dart.compiler.ast.DartAnnotation;
import com.google.dart.compiler.ast.DartArrayAccess;
import com.google.dart.compiler.ast.DartArrayLiteral;
import com.google.dart.compiler.ast.DartAssertStatement;
import com.google.dart.compiler.ast.DartBinaryExpression;
import com.google.dart.compiler.ast.DartBlock;
import com.google.dart.compiler.ast.DartBooleanLiteral;
import com.google.dart.compiler.ast.DartBreakStatement;
import com.google.dart.compiler.ast.DartCascadeExpression;
import com.google.dart.compiler.ast.DartCase;
import com.google.dart.compiler.ast.DartCatchBlock;
import com.google.dart.compiler.ast.DartClass;
import com.google.dart.compiler.ast.DartClassTypeAlias;
import com.google.dart.compiler.ast.DartConditional;
import com.google.dart.compiler.ast.DartContinueStatement;
import com.google.dart.compiler.ast.DartDeclaration;
import com.google.dart.compiler.ast.DartDefault;
import com.google.dart.compiler.ast.DartDirective;
import com.google.dart.compiler.ast.DartDoWhileStatement;
import com.google.dart.compiler.ast.DartDoubleLiteral;
import com.google.dart.compiler.ast.DartEmptyStatement;
import com.google.dart.compiler.ast.DartExportDirective;
import com.google.dart.compiler.ast.DartExprStmt;
import com.google.dart.compiler.ast.DartExpression;
import com.google.dart.compiler.ast.DartField;
import com.google.dart.compiler.ast.DartFieldDefinition;
import com.google.dart.compiler.ast.DartForInStatement;
import com.google.dart.compiler.ast.DartForStatement;
import com.google.dart.compiler.ast.DartFunction;
import com.google.dart.compiler.ast.DartFunctionExpression;
import com.google.dart.compiler.ast.DartFunctionObjectInvocation;
import com.google.dart.compiler.ast.DartFunctionTypeAlias;
import com.google.dart.compiler.ast.DartIdentifier;
import com.google.dart.compiler.ast.DartIfStatement;
import com.google.dart.compiler.ast.DartImportDirective;
import com.google.dart.compiler.ast.DartInitializer;
import com.google.dart.compiler.ast.DartIntegerLiteral;
import com.google.dart.compiler.ast.DartLabel;
import com.google.dart.compiler.ast.DartLibraryDirective;
import com.google.dart.compiler.ast.DartMapLiteral;
import com.google.dart.compiler.ast.DartMapLiteralEntry;
import com.google.dart.compiler.ast.DartMethodDefinition;
import com.google.dart.compiler.ast.DartMethodInvocation;
import com.google.dart.compiler.ast.DartNamedExpression;
import com.google.dart.compiler.ast.DartNativeBlock;
import com.google.dart.compiler.ast.DartNativeDirective;
import com.google.dart.compiler.ast.DartNewExpression;
import com.google.dart.compiler.ast.DartNode;
import com.google.dart.compiler.ast.DartNodeWithMetadata;
import com.google.dart.compiler.ast.DartNullLiteral;
import com.google.dart.compiler.ast.DartParameter;
import com.google.dart.compiler.ast.DartParameterizedTypeNode;
import com.google.dart.compiler.ast.DartParenthesizedExpression;
import com.google.dart.compiler.ast.DartPartOfDirective;
import com.google.dart.compiler.ast.DartPropertyAccess;
import com.google.dart.compiler.ast.DartRedirectConstructorInvocation;
import com.google.dart.compiler.ast.DartReturnBlock;
import com.google.dart.compiler.ast.DartReturnStatement;
import com.google.dart.compiler.ast.DartSourceDirective;
import com.google.dart.compiler.ast.DartStatement;
import com.google.dart.compiler.ast.DartStringInterpolation;
import com.google.dart.compiler.ast.DartStringLiteral;
import com.google.dart.compiler.ast.DartSuperConstructorInvocation;
import com.google.dart.compiler.ast.DartSuperExpression;
import com.google.dart.compiler.ast.DartSwitchMember;
import com.google.dart.compiler.ast.DartSwitchStatement;
import com.google.dart.compiler.ast.DartSyntheticErrorExpression;
import com.google.dart.compiler.ast.DartSyntheticErrorIdentifier;
import com.google.dart.compiler.ast.DartSyntheticErrorStatement;
import com.google.dart.compiler.ast.DartThisExpression;
import com.google.dart.compiler.ast.DartThrowExpression;
import com.google.dart.compiler.ast.DartTryStatement;
import com.google.dart.compiler.ast.DartTypeExpression;
import com.google.dart.compiler.ast.DartTypeNode;
import com.google.dart.compiler.ast.DartTypeParameter;
import com.google.dart.compiler.ast.DartUnaryExpression;
import com.google.dart.compiler.ast.DartUnit;
import com.google.dart.compiler.ast.DartUnqualifiedInvocation;
import com.google.dart.compiler.ast.DartVariable;
import com.google.dart.compiler.ast.DartVariableStatement;
import com.google.dart.compiler.ast.DartWhileStatement;
import com.google.dart.compiler.ast.HasObsoleteMetadata;
import com.google.dart.compiler.ast.ImportCombinator;
import com.google.dart.compiler.ast.ImportHideCombinator;
import com.google.dart.compiler.ast.ImportShowCombinator;
import com.google.dart.compiler.ast.LibraryNode;
import com.google.dart.compiler.ast.LibraryUnit;
import com.google.dart.compiler.ast.Modifiers;
import com.google.dart.compiler.metrics.CompilerMetrics;
import com.google.dart.compiler.parser.DartScanner.Location;
import com.google.dart.compiler.resolver.Elements;
import com.google.dart.compiler.util.Lists;
import com.google.dart.compiler.util.apache.StringUtils;
import java.io.IOException;
import java.io.Reader;
import java.math.BigInteger;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* The Dart parser. Parses a single compilation unit and produces a {@link DartUnit}.
* The grammar rules are taken from Dart.g revision 557.
*/
public class DartParser extends CompletionHooksParserBase {
private final Source source;
private final String sourceCode;
private final boolean isDietParse;
private final Set<String> prefixes;
private boolean allowNativeKeyword;
private final Set<Integer> errorHistory = new HashSet<Integer>();
private boolean isParsingInterface;
private boolean isTopLevelAbstract;
private int topLevelAbstractModifierPosition;
private boolean isParsingClass;
private int errorCount = 0;
/**
* Determines the maximum number of errors before terminating the parser. See
* {@link #reportError(int, ErrorCode, Object...)}.
*/
final int MAX_DEFAULT_ERRORS = Short.MAX_VALUE;
// Pseudo-keywords that should also be valid identifiers.
private static final String ABSTRACT_KEYWORD = "abstract";
private static final String AS_KEYWORD = "as";
private static final String CALL_KEYWORD = "call";
public static final String DYNAMIC_KEYWORD = "dynamic";
private static final String EXPORT_KEYWORD = "export";
private static final String EXTERNAL_KEYWORD = "external";
private static final String FACTORY_KEYWORD = "factory";
private static final String GETTER_KEYWORD = "get";
private static final String HIDE_KEYWORD = "hide";
private static final String IMPLEMENTS_KEYWORD = "implements";
private static final String IMPORT_KEYWORD = "import";
private static final String INTERFACE_KEYWORD = "interface";
private static final String LIBRARY_KEYWORD = "library";
private static final String NATIVE_KEYWORD = "native";
private static final String OF_KEYWORD = "of";
private static final String ON_KEYWORD = "on";
private static final String OPERATOR_KEYWORD = "operator";
private static final String PART_KEYWORD = "part";
private static final String PREFIX_KEYWORD = "prefix";
private static final String SETTER_KEYWORD = "set";
private static final String SHOW_KEYWORD = "show";
private static final String STATIC_KEYWORD = "static";
private static final String TYPEDEF_KEYWORD = "typedef";
// does not exist in specification
private static final String PATCH_KEYWORD = "patch";
public static final String[] PSEUDO_KEYWORDS = {
ABSTRACT_KEYWORD,
AS_KEYWORD,
DYNAMIC_KEYWORD,
EXPORT_KEYWORD,
EXTERNAL_KEYWORD,
FACTORY_KEYWORD,
GETTER_KEYWORD,
IMPLEMENTS_KEYWORD,
IMPORT_KEYWORD,
LIBRARY_KEYWORD,
OPERATOR_KEYWORD,
PART_KEYWORD,
SETTER_KEYWORD,
STATIC_KEYWORD,
TYPEDEF_KEYWORD
};
public static final Set<String> PSEUDO_KEYWORDS_SET = ImmutableSet.copyOf(PSEUDO_KEYWORDS);
public static final String[] RESERVED_WORDS = {
"break",
"case",
"catch",
"class",
"const",
"continue",
"default",
"do",
"else",
"extends",
"false",
"final",
"finally",
"for",
"if",
"in",
"is",
"new",
"null",
"return",
"super",
"switch",
"this",
"throw",
"true",
"try",
"var",
"void",
"while"};
public static final Set<String> RESERVED_WORDS_SET = ImmutableSet.copyOf(RESERVED_WORDS);
public DartParser(Source source,
String sourceCode,
boolean isDietParse,
Set<String> prefixes,
DartCompilerListener listener,
CompilerMetrics compilerMetrics) {
super(new DartParserCommentsHelper.CommentParserContext(source, sourceCode, listener, compilerMetrics));
this.source = source;
this.sourceCode = sourceCode;
this.isDietParse = isDietParse;
this.prefixes = prefixes;
this.allowNativeKeyword = source != null && PackageLibraryManager.isDartUri(source.getUri());
// check Unicode normalization
{
int indexOfDifference = StringUtils.indexOfDifference(sourceCode,
Normalizer.normalize(sourceCode, Normalizer.Form.NFC));
if (indexOfDifference != -1) {
DartCompilationError error = new DartCompilationError(source, new Location(
indexOfDifference, indexOfDifference + 1),
ParserErrorCode.INVALID_UNICODE_NORMALIZATION);
ctx.error(error);
}
}
}
public static String read(Source source) throws IOException {
return read(source.getSourceReader());
}
public static String read(Reader reader) throws IOException {
try {
return CharStreams.toString(reader);
} finally {
reader.close();
}
}
/**
* A flag indicating whether function expressions are allowed. See
* {@link #setAllowFunctionExpression(boolean)}.
*/
private boolean allowFunctionExpression = true;
/**
* 'break' (with no labels) and 'continue' stmts are not valid
* just anywhere, they must be inside a loop or a case stmt.
*
* A break with a label may be valid and is allowed through and
* checked in the resolver.
*/
private boolean inLoopStatement = false;
private boolean inCaseStatement = false;
/**
* Set the {@link #allowFunctionExpression} flag indicating whether function expressions are
* allowed, returning the old value. This is required to avoid ambiguity in a few places in the
* grammar.
*
* @param allow true if function expressions are allowed, false if not
* @return previous value of the flag, which should be restored
*/
private boolean setAllowFunctionExpression(boolean allow) {
boolean old = allowFunctionExpression;
allowFunctionExpression = allow;
return old;
}
/**
* <pre>
* compilationUnit
* : libraryDeclaration? topLevelDefinition* EOF
* ;
*
* libraryDeclaration
* : libraryDirective? importDirective* sourceDirective* resourceDirective* nativeDirective*
*
* topLevelDefinition
* : classDefinition
* | interfaceDefinition
* | functionTypeAlias
* | methodOrConstructorDeclaration functionStatementBody
* | type? getOrSet identifier formalParameterList functionStatementBody
* | CONST type? staticConstDeclarationList ';'
* | variableDeclaration ';'
* ;
* </pre>
*/
@Terminals(tokens={Token.EOS, Token.CLASS, Token.LIBRARY, Token.IMPORT, Token.SOURCE,
Token.RESOURCE, Token.NATIVE})
public DartUnit parseUnit() {
DartSource dartSource = (DartSource) source;
errorCount = 0;
try {
beginCompilationUnit();
ctx.unitAboutToCompile(dartSource, isDietParse);
DartUnit unit = new DartUnit(dartSource, isDietParse);
List<DartAnnotation> metadata = parseMetadata();
// parse any directives at the beginning of the source
metadata = parseDirectives(unit, metadata);
while (!EOS()) {
DartNodeWithMetadata node = null;
beginTopLevelElement();
isParsingClass = isParsingInterface = false;
// Check for ABSTRACT_KEYWORD.
isTopLevelAbstract = false;
topLevelAbstractModifierPosition = 0;
if (isBuiltInSpecial() && optionalPseudoKeyword(ABSTRACT_KEYWORD)) {
isTopLevelAbstract = true;
topLevelAbstractModifierPosition = position();
}
// skip "patch" before "class"
if (peek(1) == Token.CLASS && optionalPseudoKeyword(PATCH_KEYWORD)) {
}
// Parse top level element.
if (optional(Token.CLASS)) {
isParsingClass = true;
node = done(parseClass());
} else if (peekPseudoKeyword(0, INTERFACE_KEYWORD) && peek(1).equals(Token.IDENTIFIER)) {
consume(Token.IDENTIFIER);
isParsingInterface = true;
reportError(position(), ParserErrorCode.DEPRECATED_INTERFACE);
node = done(parseClass());
} else if (peekPseudoKeyword(0, TYPEDEF_KEYWORD)
&& (peek(1).equals(Token.IDENTIFIER) || peek(1).equals(Token.VOID))) {
consume(Token.IDENTIFIER);
node = done(parseTypeAlias());
} else if (looksLikeDirective()) {
reportErrorWithoutAdvancing(ParserErrorCode.DIRECTIVE_OUT_OF_ORDER);
metadata = parseDirectives(unit, metadata);
} else {
node = done(parseFieldOrMethod(false));
}
// Parsing was successful, add node.
if (node != null) {
unit.getTopLevelNodes().add(node);
setMetadata(node, metadata);
metadata = parseMetadata();
// Only "class" can be top-level abstract element.
if (isTopLevelAbstract && !isParsingClass) {
int abstractPositionEnd = topLevelAbstractModifierPosition + ABSTRACT_KEYWORD.length();
Location location = new Location(topLevelAbstractModifierPosition, abstractPositionEnd);
reportError(new DartCompilationError(source, location,
ParserErrorCode.ABSTRACT_TOP_LEVEL_ELEMENT));
}
}
}
expect(Token.EOS);
// add comments
{
List<int[]> commentLocs = ((DartParserCommentsHelper.CommentParserContext) ctx).getCommentLocs();
DartParserCommentsHelper.addComments(unit, source, sourceCode, commentLocs);
}
// done
unit.setHasParseErrors(errorCount != 0);
return done(unit);
} catch (StringInterpolationParseError exception) {
throw new InternalCompilerException("Failed to parse " + source.getUri(), exception);
}
}
/**
* Set the metadata associated with the given node to the given annotations.
*
* @param node the node with which the metadata is to be associated
* @param metadata the metadata to be associated with the node
*/
private void setMetadata(DartNodeWithMetadata node, List<DartAnnotation> annotations) {
if (node instanceof DartFieldDefinition) {
DartFieldDefinition fieldDefinition = (DartFieldDefinition) node;
List<DartField> fields = fieldDefinition.getFields();
for (DartField field : fields) {
setMetadata(field, annotations);
}
return;
}
if (annotations != null && !annotations.isEmpty()) {
node.setMetadata(annotations);
if (node instanceof HasObsoleteMetadata) {
HasObsoleteMetadata declaration = (HasObsoleteMetadata) node;
for (int i = 0, size = annotations.size(); i < size; i++) {
DartAnnotation annotation = annotations.get(i);
DartExpression nameNode = annotation.getName();
if (nameNode instanceof DartIdentifier) {
String name = ((DartIdentifier) nameNode).getName();
if (name.equals("deprecated")) {
declaration.setObsoleteMetadata(declaration.getObsoleteMetadata().makeDeprecated());
} else if (name.equals("override")) {
declaration.setObsoleteMetadata(declaration.getObsoleteMetadata().makeOverride());
}
}
}
}
}
}
private boolean looksLikeDirective() {
if (!isBuiltInSpecial()) {
return false;
}
switch(peek(0)) {
case LIBRARY:
case IMPORT:
case SOURCE:
case RESOURCE:
case NATIVE:
return true;
}
return peekPseudoKeyword(0, LIBRARY_KEYWORD) || peekPseudoKeyword(0, IMPORT_KEYWORD) || peekPseudoKeyword(0, PART_KEYWORD);
}
/**
* 'interface' and 'typedef' are valid to use as names of fields and methods, so you can't
* just blindly recover when you see them in any context. This does a further test to make
* sure they are followed by another identifier. This would be illegal as a field or method
* definition, as you cannot use 'interface' or 'typedef' as a type name.
*/
private boolean looksLikeTopLevelKeyword() {
if (peek(0).equals(Token.CLASS)) {
return true;
}
if (peekPseudoKeyword(0, INTERFACE_KEYWORD)
&& peek(1).equals(Token.IDENTIFIER)) {
return true;
} else if (peekPseudoKeyword(0, TYPEDEF_KEYWORD)
&& (peek(1).equals(Token.IDENTIFIER) || peek(1).equals(Token.VOID))) {
return true;
}
return false;
}
/**
* A version of the parser which only parses the directives of a library.
*
* TODO(jbrosenberg): consider parsing the whole file here, in order to avoid
* duplicate work. Probably requires removing use of LibraryUnit's, etc.
* Also, this minimal parse does have benefit in the incremental compilation
* case.
*/
@SuppressWarnings("deprecation")
public LibraryUnit preProcessLibraryDirectives(LibrarySource source) {
beginCompilationUnit();
LibraryUnit libUnit = new LibraryUnit(source);
parseMetadata();
if (peekPseudoKeyword(0, LIBRARY_KEYWORD)) {
DartLibraryDirective libraryDirective = parseLibraryDirective();
libUnit.setName(libraryDirective.getLibraryName());
parseMetadata();
}
while (peekPseudoKeyword(0, IMPORT_KEYWORD) || peekPseudoKeyword(0, EXPORT_KEYWORD)) {
if (peekPseudoKeyword(0, IMPORT_KEYWORD)) {
DartImportDirective importDirective = parseImportDirective();
LibraryNode importPath = new LibraryNode(importDirective);
importPath.setSourceInfo(importDirective.getSourceInfo());
libUnit.addImportPath(importPath);
}
if (peekPseudoKeyword(0, EXPORT_KEYWORD)) {
DartExportDirective exportDirective = parseExportDirective();
LibraryNode importPath = new LibraryNode(exportDirective);
importPath.setSourceInfo(exportDirective.getSourceInfo());
libUnit.addExportPath(importPath);
}
parseMetadata();
}
while (peekPseudoKeyword(0, PART_KEYWORD)) {
if (peekPseudoKeyword(1, OF_KEYWORD)) {
parsePartOfDirective();
} else {
DartSourceDirective sourceDirective = parsePartDirective();
LibraryNode sourcePath = new LibraryNode(sourceDirective.getSourceUri().getValue());
sourcePath.setSourceInfo(sourceDirective.getSourceInfo());
libUnit.addSourcePath(sourcePath);
}
parseMetadata();
}
//
// The code below is obsolete. We do not make any effort to find duplications between the old
// and the new syntax because support for the old syntax will be removed very soon.
//
if (peek(0) == Token.LIBRARY) {
beginLibraryDirective();
DartLibraryDirective libDirective = done(parseObsoleteLibraryDirective());
libUnit.setName(libDirective.getLibraryName());
parseMetadata();
}
while (peek(0) == Token.IMPORT) {
beginImportDirective();
DartImportDirective importDirective = done(parseObsoleteImportDirective());
LibraryNode importPath;
if (importDirective.getOldPrefix() != null) {
importPath =
new LibraryNode(importDirective);
} else {
importPath = new LibraryNode(importDirective.getLibraryUri().getValue());
}
importPath.setSourceInfo(importDirective.getSourceInfo());
libUnit.addImportPath(importPath);
parseMetadata();
}
while (peek(0) == Token.SOURCE) {
beginSourceDirective();
DartSourceDirective sourceDirective = done(parseSourceDirective());
LibraryNode sourcePath = new LibraryNode(sourceDirective.getSourceUri().getValue());
sourcePath.setSourceInfo(sourceDirective.getSourceInfo());
libUnit.addSourcePath(sourcePath);
parseMetadata();
}
while (peek(0) == Token.RESOURCE) {
parseResourceDirective();
parseMetadata();
}
while (peek(0) == Token.NATIVE) {
beginNativeDirective();
DartNativeDirective nativeDirective = done(parseNativeDirective());
LibraryNode nativePath = new LibraryNode(nativeDirective.getNativeUri().getValue());
nativePath.setSourceInfo(nativeDirective.getSourceInfo());
libUnit.addNativePath(nativePath);
parseMetadata();
}
// add ourselves to the list of sources, so inline dart code will be parsed
libUnit.addSourcePath(libUnit.getSelfSourcePath());
return done(libUnit);
}
private List<DartAnnotation> parseDirectives(DartUnit unit, List<DartAnnotation> metadata) {
boolean hasLibraryDirective = false;
if (peekPseudoKeyword(0, LIBRARY_KEYWORD)) {
DartLibraryDirective libraryDirective = parseLibraryDirective();
for (DartDirective directive : unit.getDirectives()) {
if (directive instanceof DartLibraryDirective) {
reportError(position(), ParserErrorCode.ONLY_ONE_LIBRARY_DIRECTIVE);
break;
}
}
unit.getDirectives().add(libraryDirective);
hasLibraryDirective = true;
setMetadata(libraryDirective, metadata);
metadata = parseMetadata();
}
while (peekPseudoKeyword(0, IMPORT_KEYWORD) || peekPseudoKeyword(0, EXPORT_KEYWORD)) {
if (peekPseudoKeyword(0, IMPORT_KEYWORD)) {
DartImportDirective importDirective = parseImportDirective();
unit.getDirectives().add(importDirective);
setMetadata(importDirective, metadata);
metadata = parseMetadata();
} else {
DartExportDirective exportDirective = parseExportDirective();
unit.getDirectives().add(exportDirective);
if (!hasLibraryDirective) {
reportError(exportDirective, ParserErrorCode.EXPORT_WITHOUT_LIBRARY_DIRECTIVE);
}
setMetadata(exportDirective, metadata);
metadata = parseMetadata();
}
}
while (peekPseudoKeyword(0, PART_KEYWORD)) {
if (peekPseudoKeyword(1, OF_KEYWORD)) {
DartPartOfDirective partOfDirective = parsePartOfDirective();
unit.getDirectives().add(partOfDirective);
setMetadata(partOfDirective, metadata);
metadata = parseMetadata();
} else {
DartSourceDirective partDirective = parsePartDirective();
unit.getDirectives().add(partDirective);
setMetadata(partDirective, metadata);
metadata = parseMetadata();
}
}
//
// The code below is obsolete. We do not make any effort to find duplications between the old
// and the new syntax because support for the old syntax will be removed very soon.
//
if (peek(0) == Token.LIBRARY) {
beginLibraryDirective();
DartLibraryDirective libraryDirective = parseObsoleteLibraryDirective();
for (DartDirective directive : unit.getDirectives()) {
if (directive instanceof DartLibraryDirective) {
reportError(position(), ParserErrorCode.ONLY_ONE_LIBRARY_DIRECTIVE);
break;
}
}
unit.getDirectives().add(libraryDirective);
done(libraryDirective);
setMetadata(libraryDirective, metadata);
metadata = parseMetadata();
}
while (peek(0) == Token.IMPORT) {
beginImportDirective();
DartImportDirective importDirective = parseObsoleteImportDirective();
unit.getDirectives().add(done(importDirective));
setMetadata(importDirective, metadata);
metadata = parseMetadata();
}
while (peek(0) == Token.SOURCE) {
beginSourceDirective();
DartSourceDirective sourceDirective = parseSourceDirective();
unit.getDirectives().add(done(sourceDirective));
setMetadata(sourceDirective, metadata);
metadata = parseMetadata();
}
while (peek(0) == Token.RESOURCE) {
parseResourceDirective();
}
while (peek(0) == Token.NATIVE) {
beginNativeDirective();
DartNativeDirective nativeDirective = parseNativeDirective();
unit.getDirectives().add(done(nativeDirective));
setMetadata(nativeDirective, metadata);
metadata = parseMetadata();
}
return metadata;
}
private DartLibraryDirective parseLibraryDirective() {
beginLibraryDirective();
next(); // "library"
DartExpression libraryName = parseLibraryName();
expect(Token.SEMICOLON);
return done(new DartLibraryDirective(libraryName));
}
private DartExpression parseLibraryName() {
beginQualifiedIdentifier();
DartExpression libraryName = parseIdentifier();
while (optional(Token.PERIOD)) {
beginQualifiedIdentifier();
DartIdentifier identifier = parseIdentifier();
libraryName = done(new DartPropertyAccess(libraryName, identifier));
}
return done(libraryName);
}
private DartLibraryDirective parseObsoleteLibraryDirective() {
expect(Token.LIBRARY);
reportDeprecatedError(position(), ParserErrorCode.DEPRECATED_LIBRARY_DIRECTIVE);
expect(Token.LPAREN);
beginLiteral();
expect(Token.STRING);
DartStringLiteral libname = done(DartStringLiteral.get(ctx.getTokenString()));
expectCloseParen();
expect(Token.SEMICOLON);
return new DartLibraryDirective(libname);
}
protected DartExportDirective parseExportDirective() {
beginExportDirective();
next(); // "export"
DartStringLiteral libUri = parseUri();
List<ImportCombinator> combinators = new ArrayList<ImportCombinator>();
while (peekPseudoKeyword(0, HIDE_KEYWORD) || peekPseudoKeyword(0, SHOW_KEYWORD)) {
beginImportCombinator();
if (optionalPseudoKeyword(HIDE_KEYWORD)) {
List<DartIdentifier> hiddenNames = parseIdentifierList();
combinators.add(done(new ImportHideCombinator(hiddenNames)));
} else if (optionalPseudoKeyword(SHOW_KEYWORD)) {
List<DartIdentifier> shownNames = parseIdentifierList();
combinators.add(done(new ImportShowCombinator(shownNames)));
}
}
if (!optional(Token.SEMICOLON)) {
// If there is no semicolon, then we probably don't want to consume the next token. It might
// make sense to advance to the next valid token for a directive or top-level declaration, but
// our recovery mechanism isn't quite sophisticated enough for that.
reportUnexpectedToken(position(), Token.SEMICOLON, peek(0));
}
return done(new DartExportDirective(libUri, combinators));
}
protected DartImportDirective parseImportDirective() {
beginImportDirective();
next(); // "import"
DartStringLiteral libUri = parseUri();
// allow "native" if we have "dart-ext:" import
if (StringUtils.startsWith(libUri.getValue(), "dart-ext:")) {
allowNativeKeyword = true;
}
DartIdentifier prefix = null;
if (peek(0) == Token.IDENTIFIER && "as".equals(ctx.peekTokenString(0))) {
ctx.advance();
prefix = parseIdentifier();
if (prefix instanceof DartSyntheticErrorIdentifier) {
if (peekPseudoKeyword(1, HIDE_KEYWORD) || peekPseudoKeyword(1, SHOW_KEYWORD)
|| peek(1) == Token.BIT_AND || peek(1) == Token.COLON) {
next();
}
}
}
List<ImportCombinator> combinators = new ArrayList<ImportCombinator>();
while (peekPseudoKeyword(0, HIDE_KEYWORD) || peekPseudoKeyword(0, SHOW_KEYWORD)) {
if (optionalPseudoKeyword(HIDE_KEYWORD)) {
List<DartIdentifier> hiddenNames = parseIdentifierList();
combinators.add(new ImportHideCombinator(hiddenNames));
} else if (optionalPseudoKeyword(SHOW_KEYWORD)) {
List<DartIdentifier> shownNames = parseIdentifierList();
combinators.add(new ImportShowCombinator(shownNames));
}
}
if (!optional(Token.SEMICOLON)) {
// If there is no semicolon, then we probably don't want to consume the next token. It might
// make sense to advance to the next valid token for a directive or top-level declaration, but
// our recovery mechanism isn't quite sophisticated enough for that.
reportUnexpectedToken(position(), Token.SEMICOLON, peek(0));
}
return done(new DartImportDirective(libUri, prefix, combinators));
}
/**
* Parse a comma-separated list of identifiers.
*
* @return the identifiers that were parsed
*/
private List<DartIdentifier> parseIdentifierList() {
ArrayList<DartIdentifier> identifiers = new ArrayList<DartIdentifier>();
identifiers.add(parseIdentifier());
while (optional(Token.COMMA)) {
identifiers.add(parseIdentifier());
}
return identifiers;
}
protected DartImportDirective parseObsoleteImportDirective() {
expect(Token.IMPORT);
reportDeprecatedError(position(), ParserErrorCode.DEPRECATED_IMPORT_DIRECTIVE);
expect(Token.LPAREN);
DartStringLiteral libUri = parseUri();
// allow "native" if we have "dart-ext:" import
if (StringUtils.startsWith(libUri.getValue(), "dart-ext:")) {
allowNativeKeyword = true;
}
DartBooleanLiteral export = null;
List<ImportCombinator> combinators = new ArrayList<ImportCombinator>();
DartStringLiteral prefix = null;
if (optional(Token.COMMA)) {
if (optionalPseudoKeyword(PREFIX_KEYWORD)) {
expect(Token.COLON);
beginLiteral();
expect(Token.STRING);
String id = ctx.getTokenString();
// The specification requires the value of this string be a valid identifier
if(id == null || !id.matches("[_a-zA-Z]([_A-Za-z0-9]*)")) {
reportError(position(), ParserErrorCode.EXPECTED_PREFIX_IDENTIFIER);
}
prefix = done(DartStringLiteral.get(ctx.getTokenString()));
} else {
reportError(position(), ParserErrorCode.EXPECTED_PREFIX_KEYWORD);
}
}
expectCloseParen();
expect(Token.SEMICOLON);
return new DartImportDirective(libUri, export, combinators, prefix);
}
private DartSourceDirective parsePartDirective() {
beginPartDirective();
next(); // "part"
beginLiteral();
expect(Token.STRING);
DartStringLiteral partUri = done(DartStringLiteral.get(ctx.getTokenString()));
expect(Token.SEMICOLON);
return done(new DartSourceDirective(partUri));
}
private DartSourceDirective parseSourceDirective() {
expect(Token.SOURCE);
reportDeprecatedError(position(), ParserErrorCode.DEPRECATED_SOURCE_DIRECTIVE);
expect(Token.LPAREN);
DartStringLiteral sourceUri = parseUri();
expectCloseParen();
expect(Token.SEMICOLON);
return new DartSourceDirective(sourceUri);
}
private DartPartOfDirective parsePartOfDirective() {
beginPartOfDirective();
next(); // "part"
next(); // "of"
int ofOffset= position();
DartExpression libraryName = parseLibraryName();
expect(Token.SEMICOLON);
return done(new DartPartOfDirective(ofOffset, libraryName));
}
private void parseResourceDirective() {
expect(Token.RESOURCE);
reportError(position(), ParserErrorCode.DEPRECATED_RESOURCE_DIRECTIVE);
expect(Token.LPAREN);
@SuppressWarnings("unused")
DartStringLiteral sourceUri = parseUri();
expectCloseParen();
expect(Token.SEMICOLON);
}
private DartNativeDirective parseNativeDirective() {
expect(Token.NATIVE);
expect(Token.LPAREN);
DartStringLiteral nativeUri = parseUri();
expect(Token.RPAREN);
expect(Token.SEMICOLON);
return new DartNativeDirective(nativeUri);
}
private List<DartAnnotation> parseMetadata() {
List<DartAnnotation> metadata = new ArrayList<DartAnnotation>();
while (match(Token.AT)) {
beginMetadata();
next();
beginQualifiedIdentifier();
DartExpression name = parseQualified(true);
if (optional(Token.PERIOD)) {
name = new DartPropertyAccess(name, parseIdentifier());
}
done(name);
List<DartExpression> arguments = null;
if (match(Token.LPAREN)) {
arguments = parseArguments();
}
metadata.add(done(new DartAnnotation(name, arguments)));
}
return metadata;
}
/**
* <pre>
* typeParameter
* : identifier (EXTENDS type)?
* ;
*
* typeParameters
* : '<' typeParameter (',' typeParameter)* '>'
* ;
* </pre>
*/
@Terminals(tokens={Token.GT, Token.COMMA})
private List<DartTypeParameter> parseTypeParameters() {
List<DartTypeParameter> types = new ArrayList<DartTypeParameter>();
expect(Token.LT);
do {
DartTypeParameter typeParameter = parseTypeParameter();
types.add(typeParameter);
} while (optional(Token.COMMA));
expect(Token.GT);
return types;
}
/**
* Parses single {@link DartTypeParameter} for {@link #parseTypeParameters()}.
*/
private DartTypeParameter parseTypeParameter() {
beginTypeParameter();
List<DartAnnotation> metadata = parseMetadata();
DartIdentifier name = parseIdentifier();
if (PSEUDO_KEYWORDS_SET.contains(name.getName())) {
reportError(name, ParserErrorCode.BUILT_IN_IDENTIFIER_AS_TYPE_VARIABLE_NAME);
}
// Try to parse bound.
DartTypeNode bound = null;
if (peek(0) != Token.EOS && peek(0) != Token.COMMA && peek(0) != Token.GT) {
if (optional(Token.EXTENDS)) {
// OK, this is EXTENDS, parse type.
bound = parseTypeAnnotation();
} else if (looksLikeTopLevelKeyword()) {
return done(new DartTypeParameter(name, bound));
} else if (peek(0) == Token.IDENTIFIER && (peek(1) == Token.COMMA || peek(1) == Token.GT)) {
// <X exte{cursor}>
// User tries to type "extends", but it is not finished yet.
// Report problem and try to continue.
next();
reportError(position(), ParserErrorCode.EXPECTED_EXTENDS);
} else if (peek(0) == Token.IDENTIFIER
&& peek(1) == Token.IDENTIFIER
&& (peek(2) == Token.COMMA || peek(2) == Token.GT)) {
// <X somethingLikeExtends Type>
// User mistyped word "extends" or it is not finished yet.
// Report problem and try to continue.
next();
reportError(position(), ParserErrorCode.EXPECTED_EXTENDS);
bound = parseTypeAnnotation();
} else {
// Something else, restart parsing from next top level element.
next();
reportError(position(), ParserErrorCode.EXPECTED_EXTENDS);
}
}
// Ready to create DartTypeParameter.
DartTypeParameter parameter = new DartTypeParameter(name, bound);
parameter.setMetadata(metadata);
return done(parameter);
}
private List<DartTypeParameter> parseTypeParametersOpt() {
return (peek(0) == Token.LT)
? parseTypeParameters()
: Collections.<DartTypeParameter>emptyList();
}
/**
* <pre>
* classDefinition
* : CLASS identifier typeParameters? superclass? interfaces?
* '{' classMemberDefinition* '}'
* ;
*
* superclass
* : EXTENDS type
* ;
*
* interfaces
* : IMPLEMENTS typeList
* ;
*
* superinterfaces
* : EXTENDS typeList
* ;
*
* classMemberDefinition
* : declaration ';'
* | methodDeclaration blockOrNative
*
* interfaceDefinition
* : INTERFACE identifier typeParameters? superinterfaces?
* (DEFAULT type)? '{' (interfaceMemberDefinition)* '}'
* ;
* </pre>
*/
private DartDeclaration<?> parseClass() {
beginClassBody();
int tokenOffset = ctx.getTokenLocation().getBegin();
int tokenLength = ctx.getTokenLocation().getEnd() - tokenOffset;
// Parse modifiers.
Modifiers modifiers = Modifiers.NONE;
if (isTopLevelAbstract) {
modifiers = modifiers.makeAbstract();
}
DartIdentifier name = parseIdentifier();
if (name.getName().equals("")) {
// something went horribly wrong.
if (peek(0).equals(Token.LBRACE)) {
parseBlock();
}
return done(null);
}
if (PSEUDO_KEYWORDS_SET.contains(name.getName())) {
reportError(name, ParserErrorCode.BUILT_IN_IDENTIFIER_AS_TYPE_NAME);
}
List<DartTypeParameter> typeParameters = parseTypeParametersOpt();
// Parse the extends and implements clauses.
DartTypeNode superType = null;
int withOffset = -1;
int implementsOffset = -1;
List<DartTypeNode> mixins = null;
List<DartTypeNode> interfaces = null;
if (isParsingInterface) {
if (optional(Token.EXTENDS)) {
interfaces = parseTypeAnnotationList();
}
} else {
boolean foundClause = true;
while (foundClause) {
if (optional(Token.EXTENDS)) {
if (mixins != null) {
reportErrorAtPosition(withOffset, withOffset + "with".length(),
ParserErrorCode.WITH_BEFORE_EXTENDS);
}
if (interfaces != null) {
reportErrorAtPosition(implementsOffset, implementsOffset + "implements".length(),
ParserErrorCode.IMPLEMENTS_BEFORE_EXTENDS);
}
if (superType == null) {
superType = parseTypeAnnotation();
} else {
reportError(position(), ParserErrorCode.MULTIPLE_EXTENDS_CLAUSES);
parseTypeAnnotation();
}
} else if (optional(Token.WITH)) {
if (mixins == null) {
withOffset = position();
mixins = parseTypeAnnotationList();
if (interfaces != null) {
reportErrorAtPosition(implementsOffset, implementsOffset + "implements".length(),
ParserErrorCode.IMPLEMENTS_BEFORE_WITH);
}
} else {
reportError(position(), ParserErrorCode.MULTIPLE_WITH_CLAUSES);
parseTypeAnnotationList();
}
} else if (optionalPseudoKeyword(IMPLEMENTS_KEYWORD)) {
if (interfaces == null) {
implementsOffset = position();
interfaces = parseTypeAnnotationList();
} else {
reportError(position(), ParserErrorCode.MULTIPLE_IMPLEMENTS_CLAUSES);
parseTypeAnnotationList();
}
} else {
foundClause = false;
}
}
if (mixins != null && superType == null) {
reportErrorAtPosition(withOffset, withOffset + "with".length(),
ParserErrorCode.WITH_WITHOUT_EXTENDS);
}
}
// Deal with factory clause for interfaces.
DartParameterizedTypeNode defaultClass = null;
int defaultTokenOffset = -1;
if (isParsingInterface &&
(optionalDeprecatedFactory() || optional(Token.DEFAULT))) {
defaultTokenOffset = position();
beginTypeAnnotation();
DartExpression qualified = parseQualified(false);
List<DartTypeParameter> defaultTypeParameters = parseTypeParametersOpt();
defaultClass = doneWithoutConsuming(new DartParameterizedTypeNode(qualified,
defaultTypeParameters));
}
// Deal with native clause for classes.
DartStringLiteral nativeName = null;
if (optionalPseudoKeyword(NATIVE_KEYWORD)) {
if (isParsingInterface) {
reportError(position(), ParserErrorCode.NATIVE_ONLY_CLASS);
}
if (!allowNativeKeyword) {
reportError(position(), ParserErrorCode.NATIVE_ONLY_CORE_LIB);
}
beginLiteral();
if (expect(Token.STRING)) {
nativeName = done(DartStringLiteral.get(ctx.getTokenString()));
}
modifiers = modifiers.makeNative();
}
// Parse the members.
int openBraceOffset = -1;
int closeBraceOffset = -1;
List<DartNode> members = new ArrayList<DartNode>();
if (optional(Token.LBRACE)) {
openBraceOffset = ctx.getTokenLocation().getBegin();
parseClassOrInterfaceBody(members);
expectCloseBrace(true);
closeBraceOffset = ctx.getTokenLocation().getBegin();
} else {
reportErrorWithoutAdvancing(ParserErrorCode.EXPECTED_CLASS_DECLARATION_LBRACE);
}
if (isParsingInterface) {
return done(new DartClass(tokenOffset, tokenLength, name, null, superType, implementsOffset,
interfaces, mixins, defaultTokenOffset, openBraceOffset, closeBraceOffset, members,
typeParameters, defaultClass, true, Modifiers.NONE));
} else {
return done(new DartClass(tokenOffset, tokenLength, name, nativeName, superType,
implementsOffset, interfaces, mixins, defaultTokenOffset, openBraceOffset,
closeBraceOffset, members, typeParameters, null, false, modifiers));
}
}
/**
* Helper for {@link #parseClass()}.
*
* classMemberDefinition*
*/
@Terminals(tokens={Token.RBRACE, Token.SEMICOLON})
private void parseClassOrInterfaceBody(List<DartNode> members) {
while (!match(Token.RBRACE) && !EOS() && !looksLikeTopLevelKeyword()) {
List<DartAnnotation> metadata = parseMetadata();
DartNodeWithMetadata member = parseFieldOrMethod(true);
if (member != null) {
setMetadata(member, metadata);
members.add(member);
}
// Recover at a semicolon
if (optional(Token.SEMICOLON)) {
reportUnexpectedToken(position(), null, Token.SEMICOLON);
}
}
}
private boolean optionalDeprecatedFactory() {
if (optionalPseudoKeyword(FACTORY_KEYWORD)) {
reportError(position(), ParserErrorCode.DEPRECATED_USE_OF_FACTORY_KEYWORD);
return true;
}
return false;
}
private List<DartTypeNode> parseTypeAnnotationList() {
List<DartTypeNode> result = new ArrayList<DartTypeNode>();
do {
result.add(parseTypeAnnotation());
} while (optional(Token.COMMA));
return result;
}
/**
* Look ahead to detect if we are seeing ident [ TypeParameters ] "(".
* We need this lookahead to distinguish between the optional return type
* and the alias name of a function type alias.
* Token position remains unchanged.
*
* @return true if the next tokens should be parsed as a type
*/
private boolean isFunctionTypeAliasName() {
beginFunctionTypeInterface();
try {
if (peek(0) == Token.IDENTIFIER && peek(1) == Token.LPAREN) {
return true;
}
if (peek(0) == Token.IDENTIFIER && peek(1) == Token.LT) {
consume(Token.IDENTIFIER);
// isTypeParameter leaves the position advanced if it matches
if (isTypeParameter() && peek(0) == Token.LPAREN) {
return true;
}
}
return false;
} finally {
rollback();
}
}
/**
* Returns true if the current and next tokens can be parsed as type
* parameters. Current token position is not saved and restored.
*/
private boolean isTypeParameter() {
if (peek(0) == Token.LT) {
// We are possibly looking at type parameters. Find closing ">".
consume(Token.LT);
int nestingLevel = 1;
while (nestingLevel > 0) {
switch (peek(0)) {
case LT:
nestingLevel++;
break;
case GT:
nestingLevel--;
break;
case SAR: // >>
nestingLevel -= 2;
break;
case COMMA:
case EXTENDS:
case IDENTIFIER:
break;
default:
// We are looking at something other than type parameters.
return false;
}
next();
if (nestingLevel < 0) {
return false;
}
}
}
return true;
}
/**
* Parse a type alias.
*
* <pre>
* typeAlias ::=
* 'typedef' typeAliasBody
*
* typeAliasBody ::=
* classTypeAlias
* | functionTypeAlias
*
* classTypeAlias ::=
* identifier typeParameters? '=' 'abstract'? mixinApplication
*
* mixinApplication ::=
* qualified withClause implementsClause? ';'
*
* functionTypeAlias ::=
* functionPrefix typeParameterList? formalParameterList ';'
*
* functionPrefix ::=
* returnType? name
* </pre>
*
* @return the type alias that was parsed
*/
private DartNodeWithMetadata parseTypeAlias() {
if (match(Token.IDENTIFIER)) {
Token next = peek(1);
if (next == Token.LT) {
int offset = skipTypeArguments(1, new DepthCounter());
next = peek(offset);
if (next != null && next == Token.ASSIGN) {
return parseClassTypeAlias();
}
} else if (next == Token.ASSIGN) {
return parseClassTypeAlias();
}
}
return parseFunctionTypeAlias();
}
/**
* Parse a class type alias.
*
* <pre>
* classTypeAlias ::=
* identifier typeParameters? '=' 'abstract'? mixinApplication
*
* mixinApplication ::=
* type withClause implementsClause? ';'
* </pre>
*
* @return the class type alias that was parsed
*/
private DartClassTypeAlias parseClassTypeAlias() {
beginClassTypeInterface();
DartIdentifier name = parseIdentifier();
if (PSEUDO_KEYWORDS_SET.contains(name.getName())) {
reportError(name, ParserErrorCode.BUILT_IN_IDENTIFIER_AS_TYPEDEF_NAME);
}
List<DartTypeParameter> typeParameters = parseTypeParametersOpt();
expect(Token.ASSIGN);
Modifiers modifiers = Modifiers.NONE;
if (optionalPseudoKeyword(ABSTRACT_KEYWORD)) {
modifiers = modifiers.makeAbstract();
}
DartTypeNode superType = parseTypeAnnotation();
List<DartTypeNode> mixins = null;
if (optional(Token.WITH)) {
mixins = parseTypeAnnotationList();
}
List<DartTypeNode> interfaces = null;
if (optionalPseudoKeyword(IMPLEMENTS_KEYWORD)) {
interfaces = parseTypeAnnotationList();
}
expect(Token.SEMICOLON);
return done(new DartClassTypeAlias(name, typeParameters, modifiers, superType, mixins,
interfaces));
}
/**
* Parse a function type alias.
*
* <pre>
* functionTypeAlias ::=
* functionPrefix typeParameterList? formalParameterList ';'
*
* functionPrefix ::=
* returnType? name
* </pre>
*
* @return the function type alias that was parsed
*/
private DartFunctionTypeAlias parseFunctionTypeAlias() {
beginFunctionTypeInterface();
DartTypeNode returnType = null;
if (peek(0) == Token.VOID) {
returnType = parseVoidType();
} else if (!isFunctionTypeAliasName()) {
returnType = parseTypeAnnotation();
}
DartIdentifier name = parseIdentifier();
if (PSEUDO_KEYWORDS_SET.contains(name.getName())) {
reportError(name, ParserErrorCode.BUILT_IN_IDENTIFIER_AS_TYPEDEF_NAME);
}
List<DartTypeParameter> typeParameters = parseTypeParametersOpt();
FormalParameters params = parseFormalParameterList();
expect(Token.SEMICOLON);
validateNoDefaultParameterValues(
params.val,
ParserErrorCode.DEFAULT_VALUE_CAN_NOT_BE_SPECIFIED_IN_TYPEDEF);
return done(new DartFunctionTypeAlias(name, returnType, params.val, typeParameters));
}
/**
* Parse a field or method, which may be inside a class or at the top level.
*
* <pre>
* // This rule is organized in a way that may not be most readable, but
* // gives the best error messages.
* classMemberDefinition
* : declaration ';'
* | methodDeclaration bodyOrNative
* ;
*
* // Note: this syntax is not official, but used in dart_interpreter. It
* // is unlikely that Dart will support numbered natives.
* bodyOrNative
* : error=NATIVE (':' (STRING | RATIONAL_NUMBER))? ';'
* { legacy($error, "native not supported (yet)"); }
* | functionStatementBody
* ;
*
* // A method, operator, or constructor (which all should be followed by
* // a function body).
* methodDeclaration
* : factoryConstructorDeclaration
* | STATIC methodOrConstructorDeclaration
* | specialSignatureDefinition
* | methodOrConstructorDeclaration initializers?
* | namedConstructorDeclaration initializers?
* ;
*
*
* // An abstract method/operator, a field, or const constructor (which
* // all should be followed by a semicolon).
* declaration
* : constantConstructorDeclaration initializers?
* | ABSTRACT specialSignatureDefinition
* | ABSTRACT methodOrConstructorDeclaration
* | STATIC CONST type? staticConstDeclarationList
* | STATIC? variableDeclaration
* ;
*
* interfaceMemberDefinition
* : STATIC CONST type? initializedIdentifierList ';'
* | methodOrConstructorDeclaration ';'
* | constantConstructorDeclaration ';'
* | namedConstructorDeclaration ';'
* | specialSignatureDefinition ';'
* | variableDeclaration ';'
* ;
*
* variableDeclaration
* : constVarOrType identifierList
* ;
*
* methodOrConstructorDeclaration
* : typeOrFunction? identifier formalParameterList
* ;
*
* factoryConstructorDeclaration
* : FACTORY qualified ('.' identifier)? formalParameterList
* ;
*
* namedConstructorDeclaration
* : identifier typeArguments? '.' identifier formalParameterList
* ;
*
* constructorDeclaration
* : identifier typeArguments? formalParameterList
* | namedConstructorDeclaration
* ;
*
* constantConstructorDeclaration
* : CONST qualified formalParameterList
* ;
*
* specialSignatureDefinition
* : STATIC? type? getOrSet identifier formalParameterList
* | type? OPERATOR operator formalParameterList
* ;
*
* getOrSet
* : GET
* | SET
* ;
*
* operator
* : unaryOperator
* | binaryOperator
* | '[' ']' { "[]".equals($text) }?
* | '[' ']' '=' { "[]=".equals($text) }?
* | NEGATE
* | CALL
* ;
* </pre>
*
* @param allowStatic true if the static modifier is allowed
* @return a {@link DartNode} representing the grammar fragment above
*/
@Terminals(tokens={Token.SEMICOLON})
private DartNodeWithMetadata parseFieldOrMethod(boolean allowStatic) {
beginClassMember();
Modifiers modifiers = Modifiers.NONE;
if (isBuiltInSpecial() && optionalPseudoKeyword(EXTERNAL_KEYWORD)) {
modifiers = modifiers.makeExternal();
}
if (isBuiltInSpecial() && optionalPseudoKeyword(STATIC_KEYWORD)) {
if (!allowStatic) {
reportError(position(), ParserErrorCode.TOP_LEVEL_CANNOT_BE_STATIC);
} else {
if (isParsingInterface
&& peek(0) != Token.FINAL && peek(0) != Token.CONST) {
reportError(position(), ParserErrorCode.NON_FINAL_STATIC_MEMBER_IN_INTERFACE);
}
modifiers = modifiers.makeStatic();
}
}
if (isBuiltInSpecial() && optionalPseudoKeyword(ABSTRACT_KEYWORD)) {
if (modifiers.isStatic()) {
reportError(position(), ParserErrorCode.STATIC_MEMBERS_CANNOT_BE_ABSTRACT);
}
if (modifiers.isExternal()) {
reportError(position(), ParserErrorCode.EXTERNAL_ABSTRACT);
}
modifiers = modifiers.makeAbstract();
}
if (isBuiltInSpecial() && optionalPseudoKeyword(FACTORY_KEYWORD)) {
if (isParsingInterface) {
reportError(position(), ParserErrorCode.FACTORY_MEMBER_IN_INTERFACE);
}
if (modifiers.isStatic()) {
reportError(position(), ParserErrorCode.FACTORY_CANNOT_BE_STATIC);
}
if (modifiers.isAbstract()) {
reportError(position(), ParserErrorCode.FACTORY_CANNOT_BE_ABSTRACT);
}
modifiers = modifiers.makeFactory();
}
if (match(Token.VAR) || match(Token.FINAL)) {
if (modifiers.isAbstract()) {
reportError(position(), ParserErrorCode.DISALLOWED_ABSTRACT_KEYWORD);
}
if (modifiers.isFactory()) {
reportError(position(), ParserErrorCode.DISALLOWED_FACTORY_KEYWORD);
}
}
// report "abstract" warning after all other checks to don't hide error with warning
// we ignore problems if there was already reported problem after given position
if (modifiers.isAbstract()) {
reportError(position(), ParserErrorCode.DEPRECATED_ABSTRACT_METHOD);
}
if (modifiers.isFactory()) {
if (!isParsingClass) {
reportError(position(), ParserErrorCode.FACTORY_CANNOT_BE_TOP_LEVEL);
}
// Do parse factory.
DartMethodDefinition factoryNode = parseFactory(modifiers);
// If factory is not allowed, ensure that it is valid as method.
DartExpression actualName = factoryNode.getName();
if (!allowStatic && !(actualName instanceof DartIdentifier)) {
DartExpression replacementName = new DartIdentifier(actualName.toString());
factoryNode.setName(replacementName);
}
// Done.
return done(factoryNode);
}
final DartNodeWithMetadata member;
switch (peek(0)) {
case VAR: {
consume(Token.VAR);
// Check for malformed method starting with 'var' : var ^ foo() { }
if (peek(0).equals(Token.IDENTIFIER) && looksLikeMethodOrAccessorDefinition()) {
reportError(position(), ParserErrorCode.VAR_IS_NOT_ALLOWED_ON_A_METHOD_DEFINITION);
member = parseMethodOrAccessor(modifiers, null);
break;
}
member = parseFieldDeclaration(modifiers, null);
expectStatmentTerminator();
break;
}
case CONST: {
consume(Token.CONST);
modifiers = modifiers.makeConstant();
// Allow "const factory ... native" constructors for core libraries only
if (optionalPseudoKeyword(FACTORY_KEYWORD)) {
modifiers = modifiers.makeFactory();
}
if (peek(0).equals(Token.IDENTIFIER) && looksLikeMethodOrAccessorDefinition()) {
return done(parseMethod(modifiers, null));
}
// Try to find type, may be "const ^ Type field".
DartTypeNode type = null;
if (peek(1) != Token.COMMA
&& peek(1) != Token.ASSIGN
&& peek(1) != Token.SEMICOLON) {
type = parseTypeAnnotation();
}
// Parse field.
modifiers = modifiers.makeFinal();
member = parseFieldDeclaration(modifiers, type);
expectStatmentTerminator();
break;
}
case FINAL: {
consume(Token.FINAL);
modifiers = modifiers.makeFinal();
// Check for malformed method starting with 'final': final ^ foo() { }
if (peek(0).equals(Token.IDENTIFIER) && looksLikeMethodOrAccessorDefinition()) {
reportError(position(), ParserErrorCode.FINAL_IS_NOT_ALLOWED_ON_A_METHOD_DEFINITION);
member = parseMethodOrAccessor(modifiers, null);
break;
}
DartTypeNode type = null;
if (peek(1) != Token.COMMA
&& peek(1) != Token.ASSIGN
&& peek(1) != Token.SEMICOLON) {
type = parseTypeAnnotation();
// Check again for malformed method starting with 'final': final String ^ foo() { }
if (peek(0).equals(Token.IDENTIFIER) && looksLikeMethodOrAccessorDefinition()) {
reportError(position(), ParserErrorCode.FINAL_IS_NOT_ALLOWED_ON_A_METHOD_DEFINITION);
member = parseMethodOrAccessor(modifiers, null);
break;
}
}
member = parseFieldDeclaration(modifiers, type);
expectStatmentTerminator();
break;
}
case IDENTIFIER: {
// Check to see if it looks like the start of a method definition (sans type).
if (looksLikeMethodOrAccessorDefinition()) {
member = parseMethodOrAccessor(modifiers, null);
break;
}
}
//$FALL-THROUGH$
case VOID: {
// The next token may be a type specification or parameterized constructor: either a method or field.
boolean isVoidType = peek(0) == Token.VOID;
DartTypeNode type;
if (isVoidType) {
type = parseVoidType();
} else {
int nameIndex = skipTypeName(0);
if (nameIndex < 0 || peek(nameIndex) != Token.IDENTIFIER) {
// There was no type name.
type = null;
} else {
type = parseTypeAnnotation();
}
}
if (peek(1) == Token.SEMICOLON
|| peek(1) == Token.COMMA
|| peek(1) == Token.ASSIGN) {
if (modifiers.isAbstract()) {
reportError(position(), ParserErrorCode.INVALID_FIELD_DECLARATION);
}
member = parseFieldDeclaration(modifiers, type);
if (isVoidType) {
reportError(type, ParserErrorCode.VOID_FIELD);
} else if (!modifiers.isFinal() && type == null) {
reportError(position(), ParserErrorCode.INVALID_FIELD_DECLARATION);
}
expectStatmentTerminator();
} else {
member = parseMethodOrAccessor(modifiers, type);
}
break;
}
case SEMICOLON:
default: {
done(null);
reportUnexpectedToken(position(), null, next());
member = null;
break;
}
}
return member;
}
/**
* Returns true if the beginning of a method definition follows.
*
* This test is needed to disambiguate between a method that returns a type
* and a plain method.
*
* Assumes the next token has already been determined to be an identifier.
*
* The following constructs will match:
*
* : get (
* | get identifier (
* | set (
* | set identifier (
* | operator (
* | operator <op> (
* | identifier (
* | identifier DOT identifier (
* | identifier DOT identifier DOT identifier (
*
* @return <code>true</code> if the signature of a method has been found. No tokens are consumed.
*/
private boolean looksLikeMethodOrAccessorDefinition() {
assert (peek(0) == Token.IDENTIFIER );
beginMethodName(); // begin() equivalent
try {
if (isBuiltInSpecial() && peekPseudoKeyword(0, OPERATOR_KEYWORD)) {
next();
// Using 'operator' as a field name is valid
if (peek(0).equals(Token.SEMICOLON) || peek(0).equals(Token.ASSIGN)) {
return false;
}
// Using 'operator' as a method name is valid (but discouraged)
if (peek(0).equals(Token.LPAREN)) {
return true;
}
// operator call (
if (peekPseudoKeyword(0, CALL_KEYWORD) && peek(1).equals(Token.LPAREN)) {
return true;
}
// TODO(zundel): Look for valid operator overload tokens. For now just assuming
// non-idents are good enough
// operator ??? (
if (!(peek(0).equals(Token.IDENTIFIER) && peek(1).equals(Token.LPAREN))) {
return true;
}
if (peek(0).equals(Token.LBRACK) && peek(1).equals(Token.RBRACK)) {
// operator [] (
if (peek(2).equals(Token.LPAREN)) {
return true;
}
// operator []= (
if (peek(2).equals(Token.ASSIGN) && peek(3).equals(Token.LPAREN)) {
return true;
}
}
return false;
}
if (peekPseudoKeyword(0, GETTER_KEYWORD)
|| peekPseudoKeyword(0, SETTER_KEYWORD)) {
boolean isGetter = peekPseudoKeyword(0, GETTER_KEYWORD);
next();
// Using 'get' or 'set' as a field name is valid
if (peek(0).equals(Token.SEMICOLON) || peek(0).equals(Token.ASSIGN)) {
return false;
}
// Using 'get' or 'set' as a method name is valid (but discouraged)
if (peek(0).equals(Token.LPAREN)) {
return true;
}
// normal case: get foo (
if (peek(0).equals(Token.IDENTIFIER) && (isGetter || peek(1).equals(Token.LPAREN))) {
return true;
}
return false;
}
consume(Token.IDENTIFIER);
if (peek(0).equals(Token.PERIOD) && peek(1).equals(Token.IDENTIFIER)) {
consume(Token.PERIOD);
consume(Token.IDENTIFIER);
if (peek(0).equals(Token.PERIOD) && peek(1).equals(Token.IDENTIFIER)) {
consume(Token.PERIOD);
consume(Token.IDENTIFIER);
}
}
// next token should be LPAREN
return (peek(0).equals(Token.LPAREN));
} finally {
rollback();
}
}
/**
* <pre>
* factoryConstructorDeclaration
* : FACTORY qualified ('.' identifier)? formalParameterList
* ;
* </pre>
*/
private DartMethodDefinition parseFactory(Modifiers modifiers) {
beginMethodName();
DartExpression name = parseQualified(true);
if (optional(Token.PERIOD)) {
name = doneWithoutConsuming(new DartPropertyAccess(name, parseIdentifier()));
}
done(name);
FormalParameters formals = parseFormalParameterList();
int parametersCloseParen = ctx.getTokenLocation().getBegin();
// Parse redirecting factory
if (match(Token.ASSIGN)) {
next();
if (!modifiers.isFactory()) {
reportError(position(), ParserErrorCode.ONLY_FACTORIES_CAN_REDIRECT);
}
modifiers = modifiers.makeRedirectedConstructor();
DartTypeNode redirectedTypeName = parseTypeAnnotationPossiblyFollowedByName();
DartIdentifier redirectedConstructorName = null;
if (optional(Token.PERIOD)) {
redirectedConstructorName = parseIdentifier();
}
expect(Token.SEMICOLON);
DartFunction function = doneWithoutConsuming(new DartFunction(formals.val, formals.openParen,
formals.optionalOpenOffset, formals.optionalCloseOffset, parametersCloseParen, null, null));
return DartMethodDefinition.create(name, function, modifiers, redirectedTypeName,
redirectedConstructorName);
}
DartFunction function;
if (peekPseudoKeyword(0, NATIVE_KEYWORD)) {
modifiers = modifiers.makeNative();
function = new DartFunction(formals.val, formals.optionalOpenOffset,
formals.optionalOpenOffset, formals.optionalCloseOffset, parametersCloseParen,
parseNativeBlock(modifiers), null);
} else {
function = new DartFunction(formals.val, formals.optionalOpenOffset,
formals.optionalOpenOffset, formals.optionalCloseOffset, parametersCloseParen,
parseFunctionStatementBody(!modifiers.isExternal(), true), null);
}
doneWithoutConsuming(function);
return DartMethodDefinition.create(name, function, modifiers, null);
}
private DartIdentifier parseVoidIdentifier() {
beginIdentifier();
expect(Token.VOID);
return done(new DartIdentifier(Token.VOID.getSyntax()));
}
private DartTypeNode parseVoidType() {
beginTypeAnnotation();
return done(new DartTypeNode(parseVoidIdentifier()));
}
private DartMethodDefinition parseMethod(Modifiers modifiers, DartTypeNode returnType) {
DartExpression name = new DartIdentifier("");
if (modifiers.isFactory()) {
if (modifiers.isAbstract()) {
reportError(position(), ParserErrorCode.FACTORY_CANNOT_BE_ABSTRACT);
}
if (modifiers.isStatic()) {
reportError(position(), ParserErrorCode.FACTORY_CANNOT_BE_STATIC);
}
}
int arity = -1;
Token operation = null;
if (isBuiltInSpecial() && optionalPseudoKeyword(OPERATOR_KEYWORD)) {
// Overloaded operator.
if (modifiers.isStatic()) {
reportError(position(), ParserErrorCode.OPERATOR_CANNOT_BE_STATIC);
}
modifiers = modifiers.makeOperator();
beginOperatorName();
operation = next();
if (operation.isUserDefinableOperator()) {
name = done(new DartIdentifier(operation.getSyntax()));
if (operation == Token.ASSIGN_INDEX) {
arity = 2;
} else if (operation == Token.SUB) {
arity = -1;
} else if (operation.isBinaryOperator()) {
arity = 1;
} else if (operation == Token.INDEX) {
arity = 1;
} else {
assert operation.isUnaryOperator();
arity = 0;
}
} else if (operation == Token.IDENTIFIER
&& ctx.getTokenString().equals(CALL_KEYWORD)) {
name = done(new DartIdentifier(CALL_KEYWORD));
arity = -1;
} else if (operation == Token.IDENTIFIER
&& ctx.getTokenString().equals(CALL_KEYWORD)) {
name = done(new DartIdentifier(CALL_KEYWORD));
} else {
// Not a valid operator. Try to recover.
boolean found = false;
for (int i = 0; i < 4; ++i) {
if (peek(i).equals(Token.LPAREN)) {
found = true;
break;
}
}
StringBuilder buf = new StringBuilder();
buf.append(operation.getSyntax());
if (found) {
reportError(position(), ParserErrorCode.OPERATOR_IS_NOT_USER_DEFINABLE);
while(true) {
Token token = peek(0);
if (token.equals(Token.LPAREN)) {
break;
}
buf.append(next().getSyntax());
}
name = done(new DartIdentifier(buf.toString()));
} else {
reportUnexpectedToken(position(), Token.COMMENT, operation);
done(null);
}
}
} else {
beginMethodName();
// Check for getters and setters.
if (peek(1) != Token.LPAREN && optionalPseudoKeyword(GETTER_KEYWORD)) {
name = parseIdentifier();
modifiers = modifiers.makeGetter();
arity = 0;
} else if (peek(1) != Token.LPAREN && optionalPseudoKeyword(SETTER_KEYWORD)) {
name = parseIdentifier();
modifiers = modifiers.makeSetter();
arity = 1;
} else {
// Normal method or property.
name = parseIdentifier();
}
// Check for named constructor.
if (optional(Token.PERIOD)) {
name = doneWithoutConsuming(new DartPropertyAccess(name, parseIdentifier()));
if(currentlyParsingToplevel()) {
// TODO: Error recovery could find a missing brace and treat this as an expression
reportError(name, ParserErrorCode.FUNCTION_NAME_EXPECTED_IDENTIFIER);
}
if (optional(Token.PERIOD)) {
name = doneWithoutConsuming(new DartPropertyAccess(name, parseIdentifier()));
}
}
done(null);
}
// Parse the parameters definitions.
FormalParameters parametersInfo;
if (modifiers.isGetter()) {
parametersInfo = new FormalParameters(new ArrayList<DartParameter>(), -1, -1, -1);
if (peek(0) == Token.LPAREN) {
reportError(position(), ParserErrorCode.DEPRECATED_GETTER);
parametersInfo = parseFormalParameterList();
}
} else {
parametersInfo = parseFormalParameterList();
}
List<DartParameter> parameters = parametersInfo.val;
int parametersCloseParen = ctx.getTokenLocation().getBegin();
if (arity != -1) {
if (parameters.size() != arity) {
reportError(position(), ParserErrorCode.ILLEGAL_NUMBER_OF_PARAMETERS);
}
// In methods with required arity each parameter is required.
for (int i = 0, size = parameters.size(); i < size; i++) {
DartParameter parameter = parameters.get(i);
if (parameter.getModifiers().isOptional()) {
reportError(parameter, ParserErrorCode.OPTIONAL_POSITIONAL_PARAMETER_NOT_ALLOWED);
}
if (parameter.getModifiers().isNamed()) {
reportError(parameter, ParserErrorCode.NAMED_PARAMETER_NOT_ALLOWED);
}
}
} else if (operation == Token.SUB) {
if (parameters.size() != 0 && parameters.size() != 1) {
reportError(position(), ParserErrorCode.ILLEGAL_NUMBER_OF_PARAMETERS);
}
// In methods with required arity each parameter is required.
for (int i = 0, size = parameters.size(); i < size; i++) {
DartParameter parameter = parameters.get(i);
if (parameter.getModifiers().isNamed()) {
reportError(parameter, ParserErrorCode.NAMED_PARAMETER_NOT_ALLOWED);
}
}
}
// Parse redirecting factory
DartTypeNode redirectedTypeName = null;
DartIdentifier redirectedConstructorName = null;
if (match(Token.ASSIGN)) {
next();
if (!modifiers.isFactory()) {
reportError(position(), ParserErrorCode.ONLY_FACTORIES_CAN_REDIRECT);
}
modifiers = modifiers.makeRedirectedConstructor();
redirectedTypeName = parseTypeAnnotationPossiblyFollowedByName();
if (optional(Token.PERIOD)) {
redirectedConstructorName = parseIdentifier();
}
expect(Token.SEMICOLON);
DartFunction function = doneWithoutConsuming(new DartFunction(parameters,
parametersInfo.openParen, parametersInfo.optionalOpenOffset,
parametersInfo.optionalCloseOffset, parametersCloseParen, null, returnType));
return DartMethodDefinition.create(name, function, modifiers, redirectedTypeName,
redirectedConstructorName);
}
// Parse initializer expressions for constructors.
List<DartInitializer> initializers = new ArrayList<DartInitializer>();
if (match(Token.COLON) && !(isParsingInterface || modifiers.isFactory())) {
parseInitializers(initializers);
boolean isRedirectedConstructor = validateInitializers(parameters, initializers);
if (isRedirectedConstructor) {
modifiers = modifiers.makeRedirectedConstructor();
}
}
// Parse the body.
DartBlock body = null;
if (!optional(Token.SEMICOLON)) {
if (peekPseudoKeyword(0, NATIVE_KEYWORD)) {
modifiers = modifiers.makeNative();
body = parseNativeBlock(modifiers);
} else {
body = parseFunctionStatementBody(!modifiers.isExternal(), true);
}
if (body != null && modifiers.isRedirectedConstructor()) {
reportError(position(), ParserErrorCode.REDIRECTING_CONSTRUCTOR_CANNOT_HAVE_A_BODY);
}
}
DartFunction function = doneWithoutConsuming(new DartFunction(parameters,
parametersInfo.openParen, parametersInfo.optionalOpenOffset,
parametersInfo.optionalCloseOffset, parametersCloseParen, body, returnType));
return DartMethodDefinition.create(name, function, modifiers, initializers);
}
private DartBlock parseNativeBlock(Modifiers modifiers) {
beginNativeBody();
if (!optionalPseudoKeyword(NATIVE_KEYWORD)) {
throw new AssertionError();
}
if (!allowNativeKeyword) {
reportError(position(), ParserErrorCode.NATIVE_ONLY_CORE_LIB);
}
DartExpression body = null;
if (match(Token.STRING)) {
body = parseStringWithPasting();
}
if (match(Token.LBRACE) || match(Token.ARROW)) {
return done(parseFunctionStatementBody(!modifiers.isExternal(), true));
} else {
expect(Token.SEMICOLON);
return done(new DartNativeBlock(body));
}
}
private DartNodeWithMetadata parseMethodOrAccessor(Modifiers modifiers, DartTypeNode returnType) {
DartMethodDefinition method = done(parseMethod(modifiers, returnType));
// Abstract method can not have a body.
if (method.getFunction().getBody() != null) {
if (isParsingInterface) {
reportError(method.getName(), ParserErrorCode.INTERFACE_METHOD_WITH_BODY);
}
if (method.getModifiers().isAbstract()) {
reportError(method.getName(), ParserErrorCode.ABSTRACT_METHOD_WITH_BODY);
}
}
// If getter or setter, generate DartFieldDefinition instead.
if (method.getModifiers().isGetter() || method.getModifiers().isSetter()) {
DartField field = new DartField((DartIdentifier) method.getName(),
method.getModifiers().makeAbstractField(), method, null);
field.setSourceInfo(method.getSourceInfo());
DartFieldDefinition fieldDefinition =
new DartFieldDefinition(null, Lists.<DartField>create(field));
fieldDefinition.setSourceInfo(field.getSourceInfo());
return fieldDefinition;
}
// OK, use method as method.
return method;
}
/**
* <pre>
* initializers
* : ':' superCallOrFirstFieldInitializer (',' fieldInitializer)*
* | THIS ('.' identifier) formalParameterList
* ;
*
* fieldInitializer
* : (THIS '.')? identifier '=' conditionalExpression
* ;
*
* superCallOrFirstFieldInitializer
* : SUPER arguments | SUPER '.' identifier arguments
* | fieldInitializer
* ;
*
* fieldInitializer
* : (THIS '.')? identifier '=' conditionalExpression
* | THIS ('.' identifier)? arguments
* ;
* </pre>
*/
private void parseInitializers(List<DartInitializer> initializers) {
expect(Token.COLON);
do {
beginInitializer();
if (match(Token.SUPER)) {
beginSuperInitializer();
expect(Token.SUPER);
DartIdentifier constructor = null;
if (optional(Token.PERIOD)) {
constructor = parseIdentifier();
}
DartSuperConstructorInvocation superInvocation =
new DartSuperConstructorInvocation(constructor, parseArguments());
initializers.add(done(new DartInitializer(null, done(superInvocation))));
} else {
boolean hasThisPrefix = optional(Token.THIS);
if (hasThisPrefix) {
if (match(Token.LPAREN)) {
parseRedirectedConstructorInvocation(null, initializers);
continue;
}
expect(Token.PERIOD);
}
DartIdentifier name = parseIdentifier();
if (hasThisPrefix && match(Token.LPAREN)) {
parseRedirectedConstructorInvocation(name, initializers);
continue;
} else {
expect(Token.ASSIGN);
boolean save = setAllowFunctionExpression(false);
DartExpression initExpr = parseExpression();
setAllowFunctionExpression(save);
initializers.add(done(new DartInitializer(name, initExpr)));
}
}
} while (optional(Token.COMMA));
}
private void parseRedirectedConstructorInvocation(DartIdentifier name,
List<DartInitializer> initializers) {
DartRedirectConstructorInvocation redirConstructor =
new DartRedirectConstructorInvocation(name, parseArguments());
initializers.add(done(new DartInitializer(null, doneWithoutConsuming(redirConstructor))));
}
private boolean validateInitializers(List<DartParameter> parameters,
List<DartInitializer> initializers) {
// Try to find DartRedirectConstructorInvocation, check for multiple invocations.
// Check for DartSuperConstructorInvocation multiple invocations.
DartInitializer redirectInitializer = null;
boolean firstMultipleRedirectReported = false;
{
DartInitializer superInitializer = null;
boolean firstMultipleSuperReported = false;
for (DartInitializer initializer : initializers) {
if (initializer.isInvocation()) {
// DartSuperConstructorInvocation
DartExpression initializerInvocation = initializer.getValue();
if (initializerInvocation instanceof DartSuperConstructorInvocation) {
if (superInitializer != null) {
if (!firstMultipleSuperReported) {
reportError(superInitializer, ParserErrorCode.SUPER_CONSTRUCTOR_MULTIPLE);
firstMultipleSuperReported = true;
}
reportError(initializer, ParserErrorCode.SUPER_CONSTRUCTOR_MULTIPLE);
} else {
superInitializer = initializer;
}
}
// DartRedirectConstructorInvocation
if (initializerInvocation instanceof DartRedirectConstructorInvocation) {
if (redirectInitializer != null) {
if (!firstMultipleRedirectReported) {
reportError(redirectInitializer, ParserErrorCode.REDIRECTING_CONSTRUCTOR_MULTIPLE);
firstMultipleRedirectReported = true;
}
reportError(initializer, ParserErrorCode.REDIRECTING_CONSTRUCTOR_MULTIPLE);
} else {
redirectInitializer = initializer;
}
}
}
}
}
// If there is redirecting constructor, then there should be no other initializers.
if (redirectInitializer != null) {
boolean shouldRedirectInvocationReported = false;
// Implicit initializer in form of "this.id" parameter.
for (DartParameter parameter : parameters) {
if (parameter.getName() instanceof DartPropertyAccess) {
DartPropertyAccess propertyAccess = (DartPropertyAccess) parameter.getName();
if (propertyAccess.getQualifier() instanceof DartThisExpression) {
shouldRedirectInvocationReported = true;
reportError(
parameter,
ParserErrorCode.REDIRECTING_CONSTRUCTOR_PARAM);
}
}
}
// Iterate all initializers and mark all except of DartRedirectConstructorInvocation
for (DartInitializer initializer : initializers) {
if (!(initializer.getValue() instanceof DartRedirectConstructorInvocation)) {
shouldRedirectInvocationReported = true;
reportError(
initializer,
ParserErrorCode.REDIRECTING_CONSTRUCTOR_OTHER);
}
}
// Mark DartRedirectConstructorInvocation if needed.
if (shouldRedirectInvocationReported) {
reportError(
redirectInitializer,
ParserErrorCode.REDIRECTING_CONSTRUCTOR_ITSELF);
}
}
// Done.
return redirectInitializer != null;
}
/**
* <pre>
* variableDeclaration
* : constVarOrType identifierList
* ;
* identifierList
* : identifier (',' identifier)*
* ;
*
* staticConstDeclarationList
* : staticConstDeclaration (',' staticConstDeclaration)*
* ;
*
* staticConstDeclaration
* : identifier '=' constantExpression
* ;
*
* // The compile-time expression production is used to mark certain expressions
* // as only being allowed to hold a compile-time constant. The grammar cannot
* // express these restrictions, so this will have to be enforced by a separate
* // analysis phase.
* constantExpression
* : expression
* ;
* </pre>
*/
private DartFieldDefinition parseFieldDeclaration(Modifiers modifiers, DartTypeNode type) {
List<DartField> fields = new ArrayList<DartField>();
List<DartAnnotation> metadata = parseMetadata();
do {
beginVariableDeclaration();
DartIdentifier name = parseIdentifier();
DartExpression value = null;
if (optional(Token.ASSIGN)) {
value = parseExpression();
if (value != null) {
modifiers = modifiers.makeInitialized();
}
}
if (modifiers.isExternal()) {
reportError(name, ParserErrorCode.EXTERNAL_ONLY_METHOD);
}
DartField field = done(new DartField(name, modifiers, null, value));
setMetadata(field, metadata);
fields.add(field);
} while (optional(Token.COMMA));
DartFieldDefinition definition = new DartFieldDefinition(type, fields);
setMetadata(definition, metadata);
return done(definition);
}
private static class FormalParameters {
private final List<DartParameter> val;
private final int openParen;
private final int optionalOpenOffset;
private final int optionalCloseOffset;
public FormalParameters(List<DartParameter> parameters, int openParen, int optionalOpenOffset,
int optionalCloseOffset) {
this.val = parameters;
this.openParen = openParen;
this.optionalOpenOffset = optionalOpenOffset;
this.optionalCloseOffset = optionalCloseOffset;
}
}
/**
* <pre>
* formalParameterList
* : '(' ')'
* | '(' normalFormalParameters (',' optionalFormalParameters)? ')'
* | '(' optionalFormalParameters ')'
* ;
*
* normalFormalParameters
* : normalFormalParameter (',' normalFormalParameter)*
* ;
*
* optionalFormalParameters
* : optionalPositionalFormalParameters
* | namedFormalParameters
* ;
*
* optionalPositionalFormalParameters
* : '[' defaultFormalParameter (',' defaultFormalParameter)* ']'
* ;
*
* namedFormalParameters
* : '{' defaultNamedParameter (',' defaultNamedParameter)* '}'
* ;
* </pre>
*/
@Terminals(tokens = {Token.COMMA, Token.RPAREN})
private FormalParameters parseFormalParameterList() {
beginFormalParameterList();
List<DartParameter> params = new ArrayList<DartParameter>();
int optionalOpenOffset = -1;
int optionalCloseOffset = -1;
expect(Token.LPAREN);
boolean done = optional(Token.RPAREN);
int openParen = ctx.getTokenLocation().getBegin();
boolean isOptional = false;
boolean isNamed = false;
while (!done) {
if (!isOptional && optional(Token.LBRACK)) {
if (isNamed) {
reportErrorWithoutAdvancing(ParserErrorCode.CANNOT_MIX_OPTIONAL_AND_NAMED_PARAMETERS);
}
isOptional = true;
optionalOpenOffset = ctx.getTokenLocation().getBegin();
}
if (!isNamed && optional(Token.LBRACE)) {
if (isOptional) {
reportErrorWithoutAdvancing(ParserErrorCode.CANNOT_MIX_OPTIONAL_AND_NAMED_PARAMETERS);
}
isNamed = true;
optionalOpenOffset = ctx.getTokenLocation().getBegin();
}
DartParameter param = parseFormalParameter(isOptional, isNamed);
params.add(param);
if (isOptional && optional(Token.RBRACK)) {
optionalCloseOffset = ctx.getTokenLocation().getBegin();
expectCloseParen();
break;
}
if (isNamed && optional(Token.RBRACE)) {
optionalCloseOffset = ctx.getTokenLocation().getBegin();
expectCloseParen();
break;
}
// Ensure termination if token is anything other than COMMA.
// Must keep Token.COMMA in sync with @Terminals above
if (!optional(Token.COMMA)) {
if (isOptional && !optional(Token.RBRACE)) {
reportErrorWithoutAdvancing(ParserErrorCode.MISSING_OPTIONAL_PARAMETER_END);
}
if (isNamed && !optional(Token.RBRACK)) {
reportErrorWithoutAdvancing(ParserErrorCode.MISSING_NAMED_PARAMETER_END);
}
// Must keep Token.RPAREN in sync with @Terminals above
expectCloseParen();
done = true;
}
}
return new FormalParameters(done(params), openParen, optionalOpenOffset, optionalCloseOffset);
}
/**
* <pre>
* normalFormalParameter
* : functionDeclaration
* | fieldFormalParameter
* | simpleFormalParameter
* ;
*
* defaultFormalParameter
* : normalFormalParameter ('=' constantExpression)?
* ;
*
* defaultNamedParameter
* : normalFormalParameter (':' constantExpression)?
* ;
* </pre>
*/
private DartParameter parseFormalParameter(boolean isOptional, boolean isNamed) {
beginFormalParameter();
List<DartAnnotation> metadata = parseMetadata();
DartExpression paramName = null;
DartTypeNode type = null;
DartExpression defaultExpr = null;
List<DartParameter> functionParams = null;
boolean hasVar = false;
Modifiers modifiers = Modifiers.NONE;
if (isOptional) {
modifiers = modifiers.makeOptional();
}
if (isNamed) {
modifiers = modifiers.makeNamed();
}
if (optional(Token.FINAL)) {
modifiers = modifiers.makeFinal();
} else if (optional(Token.CONST)) {
reportError(position(), ParserErrorCode.FORMAL_PARAMETER_IS_CONST);
} else if (optional(Token.VAR)) {
hasVar = true;
}
boolean isVoidType = false;
if (!hasVar) {
isVoidType = (peek(0) == Token.VOID);
if (isVoidType) {
type = parseVoidType();
} else if ((peek(0) != Token.ELLIPSIS)
&& (peek(1) != Token.COMMA)
&& (peek(1) != Token.RPAREN)
&& (peek(1) != Token.RBRACE)
&& (peek(1) != Token.RBRACK)
&& (peek(1) != Token.ASSIGN)
&& (peek(1) != Token.COLON)
&& (peek(1) != Token.LPAREN)
&& (peek(0) != Token.THIS)) {
// Must be a type specification.
type = parseTypeAnnotation();
}
}
paramName = parseParameterName();
if (peek(0) == Token.LPAREN) {
// Function parameter.
if (modifiers.isFinal()) {
reportError(position(), ParserErrorCode.FUNCTION_TYPED_PARAMETER_IS_FINAL);
}
if (hasVar) {
reportError(position(), ParserErrorCode.FUNCTION_TYPED_PARAMETER_IS_VAR);
}
functionParams = parseFormalParameterList().val;
validateNoDefaultParameterValues(
functionParams,
ParserErrorCode.DEFAULT_VALUE_CAN_NOT_BE_SPECIFIED_IN_CLOSURE);
} else {
// Not a function parameter.
if (isVoidType) {
reportError(type, ParserErrorCode.VOID_PARAMETER);
}
}
// Look for an initialization expression
switch (peek(0)) {
case COMMA:
case RPAREN:
case RBRACE:
case RBRACK:
// It is a simple parameter.
break;
case ASSIGN:
// Default parameter -- only allowed for optional parameters.
if (isOptional) {
consume(Token.ASSIGN);
defaultExpr = parseExpression();
} else if (isNamed) {
reportError(position(), ParserErrorCode.INVALID_SEPARATOR_FOR_NAMED);
consume(Token.ASSIGN);
defaultExpr = parseExpression();
} else {
reportError(position(), ParserErrorCode.DEFAULT_POSITIONAL_PARAMETER);
}
break;
case COLON:
// Default parameter -- only allowed for named parameters.
if (isNamed) {
consume(Token.COLON);
defaultExpr = parseExpression();
} else if (isOptional) {
reportError(position(), ParserErrorCode.INVALID_SEPARATOR_FOR_OPTIONAL);
consume(Token.COLON);
defaultExpr = parseExpression();
} else {
reportError(position(), ParserErrorCode.DEFAULT_POSITIONAL_PARAMETER);
}
break;
default:
reportUnexpectedToken(position(), null, peek(0));
break;
}
DartParameter parameter = new DartParameter(paramName, type, functionParams, defaultExpr,
modifiers);
setMetadata(parameter, metadata);
return done(parameter);
}
/**
* <pre>
* simpleFormalParameter
* : declaredIdentifier
* | identifier
* ;
*
* fieldFormalParameter
* : finalVarOrType? THIS '.' identifier
* ;
* </pre>
*/
private DartExpression parseParameterName() {
beginParameterName();
if (match(Token.THIS)) {
beginThisExpression();
expect(Token.THIS);
DartThisExpression thisExpression = done(DartThisExpression.get());
expect(Token.PERIOD);
return done(new DartPropertyAccess(thisExpression, parseIdentifier()));
}
return done(parseIdentifier());
}
/**
* Validates that given {@link DartParameter}s have no default values, or marks existing default
* values with given {@link ErrorCode}.
*/
private void validateNoDefaultParameterValues(List<DartParameter> parameters,
ErrorCode errorCode) {
for (int i = 0, size = parameters.size(); i < size; i++) {
DartParameter parameter = parameters.get(i);
DartExpression defaultExpr = parameter.getDefaultExpr();
if (defaultExpr != null) {
reportError(defaultExpr, errorCode);
}
}
}
/**
* Parse an expression.
*
* <pre>
* expression
* : assignableExpression assignmentOperator expression
* | conditionalExpression cascadeSection*
* | throwExpression
* ;
*
* assignableExpression
* : primary (arguments* assignableSelector)+
* | SUPER assignableSelector
* | identifier
* ;
* </pre>
*
* @return an expression matching the {@code expression} production above
*/
@VisibleForTesting
public DartExpression parseExpression() {
if (peek(0) == Token.THROW) {
return parseThrowExpression(true);
}
beginExpression();
if (looksLikeTopLevelKeyword() || peek(0).equals(Token.RBRACE)) {
// Allow recovery back to the top level.
reportErrorWithoutAdvancing(ParserErrorCode.UNEXPECTED_TOKEN);
return done(null);
}
DartExpression result = parseConditionalExpression();
Token token = peek(0);
if (token == Token.CASCADE) {
List<DartExpression> cascadeSections = new ArrayList<DartExpression>();
while (token == Token.CASCADE) {
beginExpression();
DartExpression section = parseCascadeSection();
done(section);
if (section != null) {
cascadeSections.add(section);
}
token = peek(0);
}
result = done(new DartCascadeExpression(result, cascadeSections));
} else if (token.isAssignmentOperator()) {
ensureAssignable(result);
consume(token);
int tokenOffset = ctx.getTokenLocation().getBegin();
result = done(new DartBinaryExpression(token, tokenOffset, result, parseExpression()));
} else {
done(null);
}
return result;
}
/**
* Parse an expression without a cascade.
*
* <pre>
* expressionWithoutCascade
* : assignableExpression assignmentOperator expressionWithoutCascade
* | conditionalExpression
* ;
* </pre>
*
* @return an expression matching the {@code expression} production above
*/
private DartExpression parseExpressionWithoutCascade() {
if (peek(0) == Token.THROW) {
return parseThrowExpression(false);
}
beginExpression();
if (looksLikeTopLevelKeyword() || peek(0).equals(Token.RBRACE)) {
// Allow recovery back to the top level.
reportErrorWithoutAdvancing(ParserErrorCode.UNEXPECTED_TOKEN);
return done(null);
}
DartExpression result = parseConditionalExpression();
Token token = peek(0);
if (token.isAssignmentOperator()) {
ensureAssignable(result);
consume(token);
int tokenOffset = ctx.getTokenLocation().getBegin();
result = done(new DartBinaryExpression(token, tokenOffset, result, parseExpressionWithoutCascade()));
} else {
done(null);
}
return result;
}
/**
* Parse a cascade section.
* <pre>
* cascadeSection
* : CASCADE (cascadeSelector arguments*) (assignableSelector arguments*)* (assignmentOperator
* expressionWithoutCascade)?
* ;
*
* cascadeSelector
* : LBRACK expression RBRACK
* | identifier
* ;
* </pre>
*
* @return the expression representing the cascaded method invocation
*/
private DartExpression parseCascadeSection() {
expect(Token.CASCADE);
DartExpression result = null;
DartIdentifier functionName = null;
if (peek(0) == Token.IDENTIFIER) {
functionName = parseIdentifier();
} else if (peek(0) == Token.LBRACK) {
consume(Token.LBRACK);
result = doneWithoutConsuming(new DartArrayAccess(result, true, parseExpression()));
expect(Token.RBRACK);
} else {
reportUnexpectedToken(position(), null, next());
return result;
}
if (peek(0) == Token.LPAREN) {
while (peek(0) == Token.LPAREN) {
if (functionName != null) {
result = doneWithoutConsuming(new DartMethodInvocation(result, result == null, functionName, parseArguments()));
functionName = null;
} else if (result == null) {
return null;
} else {
result = doneWithoutConsuming(new DartFunctionObjectInvocation(result, parseArguments()));
}
}
} else if (functionName != null) {
result = doneWithoutConsuming(new DartPropertyAccess(result, result == null, functionName));
}
boolean progress = true;
while (progress) {
progress = false;
DartExpression selector = tryParseAssignableSelector(result);
if (selector != null) {
result = selector;
progress = true;
while (peek(0) == Token.LPAREN) {
result = doneWithoutConsuming(new DartFunctionObjectInvocation(result, parseArguments()));
}
}
}
Token token = peek(0);
if (token.isAssignmentOperator()) {
ensureAssignable(result);
consume(token);
int tokenOffset = ctx.getTokenLocation().getBegin();
result = doneWithoutConsuming(new DartBinaryExpression(token, tokenOffset, result, parseExpressionWithoutCascade()));
}
return result;
}
/**
* expressionList
* : expression (',' expression)*
* ;
*/
@Terminals(tokens={Token.COMMA})
private DartExpression parseExpressionList() {
beginExpressionList();
DartExpression result = parseExpression();
// Must keep in sync with @Terminals above
while (optional(Token.COMMA)) {
int tokenOffset = ctx.getTokenLocation().getBegin();
result = new DartBinaryExpression(Token.COMMA, tokenOffset, result, parseExpression());
if (match(Token.COMMA)) {
result = doneWithoutConsuming(result);
}
}
return done(result);
}
/**
* Parse a binary expression.
*
* <pre>
* logicalOrExpression
* : logicalAndExpression ('||' logicalAndExpression)*
* ;
*
* logicalAndExpression
* : bitwiseOrExpression ('&&' bitwiseOrExpression)*
* ;
*
* bitwiseOrExpression
* : bitwiseXorExpression ('|' bitwiseXorExpression)*
* ;
*
* bitwiseXorExpression
* : bitwiseAndExpression ('^' bitwiseAndExpression)*
* ;
*
* bitwiseAndExpression
* : equalityExpression ('&' equalityExpression)*
* ;
*
* equalityExpression
* : relationalExpression (equalityOperator relationalExpression)?
* ;
*
* relationalExpression
* : shiftExpression (isOperator type | relationalOperator shiftExpression)?
* ;
*
* shiftExpression
* : additiveExpression (shiftOperator additiveExpression)*
* ;
*
* additiveExpression
* : multiplicativeExpression (additiveOperator multiplicativeExpression)*
* ;
*
* multiplicativeExpression
* : unaryExpression (multiplicativeOperator unaryExpression)*
* ;
* </pre>
*
* @return an expression matching one of the productions above
*/
private DartExpression parseBinaryExpression(int precedence) {
assert (precedence >= 4);
beginBinaryExpression();
DartExpression lastResult = parseUnaryExpression();
DartExpression result = lastResult;
for (int level = peekMaybeAS(0).getPrecedence(); level >= precedence; level--) {
while (peekMaybeAS(0).getPrecedence() == level) {
int prevPositionStart = ctx.getTokenLocation().getBegin();
int prevPositionEnd = ctx.getTokenLocation().getEnd();
Token token = nextMaybeAS();
int tokenOffset = ctx.getTokenLocation().getBegin();
if (lastResult instanceof DartSuperExpression
&& (token == Token.AND || token == Token.OR)) {
reportErrorAtPosition(prevPositionStart, prevPositionEnd,
ParserErrorCode.SUPER_IS_NOT_VALID_AS_A_BOOLEAN_OPERAND);
}
if (token == Token.EQ_STRICT) {
reportError(tokenOffset, ParserErrorCode.DEPRECATED_STRICT_EQ);
}
if (token == Token.NE_STRICT) {
reportError(tokenOffset, ParserErrorCode.DEPRECATED_STRICT_NE);
}
DartExpression right;
if (token == Token.IS) {
beginTypeExpression();
if (optional(Token.NOT)) {
int notOffset = ctx.getTokenLocation().getBegin();
beginTypeExpression();
DartTypeExpression typeExpression = done(new DartTypeExpression(parseTypeAnnotation()));
right = done(new DartUnaryExpression(Token.NOT, notOffset, typeExpression, true));
} else {
right = done(new DartTypeExpression(parseTypeAnnotation()));
}
} else if (token == Token.AS) {
beginTypeExpression();
right = done(new DartTypeExpression(parseTypeAnnotation()));
} else {
right = parseBinaryExpression(level + 1);
}
if (right instanceof DartSuperExpression) {
reportError(position(), ParserErrorCode.SUPER_CANNOT_BE_USED_AS_THE_SECOND_OPERAND);
}
lastResult = right;
result = doneWithoutConsuming(new DartBinaryExpression(token, tokenOffset, result, right));
if (token == Token.IS
|| token == Token.AS
|| token.isRelationalOperator()
|| token.isEqualityOperator()) {
// The operations cannot be chained.
if (peekMaybeAS(0) == token) {
reportError(position(), ParserErrorCode.INVALID_OPERATOR_CHAINING,
token.toString().toLowerCase());
}
break;
}
}
}
done(null);
return result;
}
/**
* Use this method where token "as" is expected to be used as built-in identifier.
*
* @return the {@link Token} at given position or {@link Token#AS}.
*/
private Token peekMaybeAS(int n) {
Token token = ctx.peek(n);
String tokenString = ctx.peekTokenString(n);
if (token == Token.IDENTIFIER && "as".equals(tokenString)) {
return Token.AS;
}
return token;
}
/**
* Use this method where token "as" is expected to be used as built-in identifier.
*
* @return the current {@link Token} or {@link Token#AS}.
*/
private Token nextMaybeAS() {
ctx.advance();
Token token = ctx.getCurrentToken();
String tokenString = ctx.getTokenString();
if (token == Token.IDENTIFIER && "as".equals(tokenString)) {
return Token.AS;
}
return token;
}
/**
* Parse the arguments passed to a function or method invocation.
*
* <pre>
* arguments
* : '(' argumentList? ')'
* ;
*
* argumentList
* : expression (',' expression)* (',' spreadArgument)?
* | spreadArgument
* ;
*
* spreadArgument
* : '...' expression
* ;
* </pre>
*
* @return a list of expressions containing the arguments to be passed
*/
@Terminals(tokens={Token.RPAREN, Token.COMMA})
public List<DartExpression> parseArguments() {
List<DartExpression> arguments = new ArrayList<DartExpression>();
expect(Token.LPAREN);
// SEMICOLON is for error recovery
boolean namedArgumentParsed = false;
outer: while (!match(Token.RPAREN) && !match(Token.EOS) && !match(Token.SEMICOLON)) {
beginParameter();
// parse argument, may be named
DartExpression expression;
if (peek(1) == Token.COLON) {
DartIdentifier name = parseIdentifier();
expect(Token.COLON);
expression = new DartNamedExpression(name, parseExpression());
namedArgumentParsed = true;
} else {
expression = parseExpression();
if (namedArgumentParsed) {
reportError(expression, ParserErrorCode.POSITIONAL_AFTER_NAMED_ARGUMENT);
}
}
done(expression);
// add argument, if parsed successfully
if (expression != null) {
arguments.add(expression);
}
// do we have more arguments?
switch(peek(0)) {
// Must keep in sync with @Terminals above
case COMMA:
if (peek(-1) == Token.COMMA) {
reportErrorWithoutAdvancing(ParserErrorCode.EXPECTED_EXPRESSION_AFTER_COMMA);
}
consume(Token.COMMA);
break;
// Must keep in sync with @Terminals above
case RPAREN:
break;
default:
Token actual = peek(0);
Set<Token> terminals = collectTerminalAnnotations();
if (terminals.contains(actual) || looksLikeTopLevelKeyword()) {
// Looks like a method already on the stack could use this token.
reportErrorWithoutAdvancing(ParserErrorCode.EXPECTED_COMMA_OR_RIGHT_PAREN);
break outer;
} else {
// Advance the parser state if no other method on the stack can use this token.
ctx.advance();
}
reportError(ctx.getTokenLocation().getEnd(),
ParserErrorCode.EXPECTED_COMMA_OR_RIGHT_PAREN, actual);
break;
}
}
if (peek(-1) == Token.COMMA) {
reportErrorWithoutAdvancing(ParserErrorCode.EXPECTED_EXPRESSION_AFTER_COMMA);
}
expectCloseParen();
return arguments;
}
/**
* Parse a conditional expression.
*
* <pre>
* conditionalExpression
* : logicalOrExpression ('?' expression ':' expression)?
* ;
* </pre>
*
* @return an expression matching the {@code conditionalExpression} production
*/
private DartExpression parseConditionalExpression() {
beginConditionalExpression();
DartExpression result = parseBinaryExpression(4);
if (result instanceof DartSuperExpression) {
reportError(position(), ParserErrorCode.SUPER_IS_NOT_VALID_ALONE_OR_AS_A_BOOLEAN_OPERAND);
}
if (peek(0) != Token.CONDITIONAL) {
return done(result);
}
consume(Token.CONDITIONAL);
DartExpression yes = parseExpressionWithoutCascade();
expect(Token.COLON);
DartExpression no = parseExpressionWithoutCascade();
return done(new DartConditional(result, yes, no));
}
private boolean looksLikeStringInterpolation() {
int peekAhead = 0;
while (true) {
switch (peek(peekAhead++)) {
case STRING:
break;
case STRING_SEGMENT:
case STRING_LAST_SEGMENT:
case STRING_EMBED_EXP_START:
case STRING_EMBED_EXP_END:
return true;
default:
return false;
}
}
}
/**
* Pastes together adjacent strings. Re-uses the StringInterpolation
* node if there is more than one adjacent string.
*/
private DartExpression parseStringWithPasting() {
List<DartExpression> expressions = new ArrayList<DartExpression>();
if (looksLikeStringInterpolation()) {
beginStringInterpolation();
} else {
beginLiteral();
}
DartExpression result = null;
boolean foundStringInterpolation = false;
do {
result = null;
switch(peek(0)) {
case STRING:
case STRING_SEGMENT:
case STRING_EMBED_EXP_START:
// another string is coming, glue it together.
result = parseString();
if (result != null) {
expressions.add(result);
}
if (result instanceof DartStringInterpolation) {
foundStringInterpolation = true;
}
break;
}
} while (result != null);
if (expressions.size() == 0) {
return doneWithoutConsuming(null);
} else if (expressions.size() == 1) {
return done(expressions.get(0));
}
if (foundStringInterpolation) {
DartStringInterpolationBuilder builder = new DartStringInterpolationBuilder();
// Create a new DartStringInterpolation object from the expressions.
boolean first = true;
for (DartExpression expr : expressions) {
if (!first) {
// pad between interpolations with a dummy expression
builder.addExpression(DartStringLiteral.get(""));
}
if (expr instanceof DartStringInterpolation) {
builder.addInterpolation((DartStringInterpolation)expr);
} else if (expr instanceof DartStringLiteral) {
builder.addString((DartStringLiteral)expr);
} else {
throw new InternalCompilerException("Expected String or StringInterpolation");
}
first = false;
}
return done(builder.buildInterpolation());
}
// Synthesize a single String literal
List<DartStringLiteral> stringParts = new ArrayList<DartStringLiteral>();
StringBuilder builder = new StringBuilder();
for (DartExpression expr : expressions) {
DartStringLiteral stringPart = (DartStringLiteral)expr;
stringParts.add(stringPart);
builder.append(stringPart.getValue());
}
return done(DartStringLiteral.get(builder.toString(), stringParts));
}
private DartExpression parseString() {
switch(peek(0)) {
case STRING: {
beginLiteral();
consume(Token.STRING);
return done(DartStringLiteral.get(ctx.getTokenString()));
}
case STRING_SEGMENT:
case STRING_EMBED_EXP_START:
return parseStringInterpolation();
default:
DartExpression expression = parseExpression();
reportError(position(), ParserErrorCode.EXPECTED_STRING_LITERAL);
return expression;
}
}
private int skipStringLiteral(int offset) {
Token token = peek(offset);
while (token == Token.STRING || token == Token.STRING_SEGMENT || token == Token.STRING_EMBED_EXP_START) {
switch(token) {
case STRING:
offset = offset + 1;
case STRING_SEGMENT:
case STRING_EMBED_EXP_START:
offset = skipStringInterpolation(offset);
}
token = peek(offset);
}
return offset;
}
private int skipStringInterpolation(int offset) {
Token token = peek(offset);
if (token == Token.STRING_LAST_SEGMENT) {
return -1;
}
boolean inString = true;
while (inString) { // Iterate until we find the last string segment.
switch (token) {
case STRING_SEGMENT:
offset = offset + 1;
token = peek(offset);
break;
case STRING_LAST_SEGMENT:
offset = offset + 1;
token = peek(offset);
inString = false;
break;
case STRING_EMBED_EXP_START: {
offset = offset + 1;
token = peek(offset);
while (token != Token.EOS && token != Token.STRING_EMBED_EXP_END && token != Token.STRING_LAST_SEGMENT) {
if (token == Token.STRING || token == Token.STRING_SEGMENT || token == Token.STRING_EMBED_EXP_START) {
offset = skipStringLiteral(offset);
} else {
offset = offset + 1;
}
token = peek(offset);
}
if (token != Token.STRING_EMBED_EXP_END) {
inString = Token.STRING_LAST_SEGMENT != token;
}
break;
}
default:
inString = false;
break;
}
}
return offset;
}
private DartStringLiteral parseUri() {
DartExpression str = parseStringWithPasting();
if (str instanceof DartStringLiteral) {
return (DartStringLiteral) str;
} else if (str != null) {
reportError(str, ParserErrorCode.URI_CANNOT_USE_INTERPOLATION);
DartStringLiteral result = DartStringLiteral.get("<invalid-uri>");
result.setSourceInfo(str.getSourceInfo());
return result;
} else {
expect(Token.STRING);
return DartStringLiteral.get(null);
}
}
/**
* Instances of the class {@code DepthCounter} represent the number of less than tokens that have
* not yet been matched.
*/
private static class DepthCounter {
/**
* The number of less than tokens that have not yet been matched.
*/
private int count = 0;
/**
* Increment the number of less than tokens that have not yet been matched by the given amount
* (or decrement the count if the argument is negative).
*
* @param value the amount by which the count should be changed
* @return the count after it has been modified
*/
public int add(int value) {
count += value;
return count;
}
/**
* Return the number of less than tokens that have not yet been matched.
*
* @return the number of less than tokens that have not yet been matched
*/
public int getCount() {
return count;
}
}
/**
* Return the offset of the first token after a type name, or {@code -1} if the token at the given
* offset is not the start of a type name.
*
* @param offset the offset of the first token of the type name
* @return the offset of the first token after a type name
*/
private int skipTypeName(int offset) {
return skipTypeName(offset, new DepthCounter());
}
/**
* Return the offset of the first token after a type name, or {@code -1} if the token at the given
* offset is not the start of a type name.
*
* @param offset the offset of the first token of the type name
* @param depth the number of less-thans that have been encountered since the outer-most type name
* @return the offset of the first token after a type name
*/
private int skipTypeArguments(int offset, DepthCounter depth) {
if (peek(offset) != Token.LT) {
return -1;
}
int oldDepth = depth.add(1);
offset = skipTypeName(offset + 1, depth);
if (offset < 0) {
return offset;
}
while (peek(offset) == Token.COMMA) {
offset = skipTypeName(offset + 1, depth);
if (offset < 0) {
return offset;
}
}
if (depth.getCount() < oldDepth) {
// We already passed the closing '>' for this list of type arguments
return offset;
}
if (peek(offset) == Token.GT) {
depth.add(-1);
return offset + 1;
} else if (peek(offset) == Token.SAR) {
depth.add(-2);
return offset + 1;
}
return -1;
}
/**
* Return the offset of the first token after a type name, or {@code -1} if the token at the given
* offset is not the start of a type name.
*
* @param offset the offset of the first token of the type name
* @param depth the number of less-thans that have been encountered since the outer-most type name
* @return the offset of the first token after a type name
*/
private int skipTypeName(int offset, DepthCounter depth) {
if (peek(offset) != Token.IDENTIFIER) {
return -1;
}
offset++;
if (peek(offset) == Token.PERIOD) {
offset++;
if (peek(offset) == Token.IDENTIFIER) {
// We tolerate a missing identifier in order to recover better
offset++;
}
}
if (peek(offset) == Token.LT) {
offset = skipTypeArguments(offset, depth);
}
return offset;
}
/**
* Parse any literal that is not a function literal (those have already been
* handled before this method is called, so we don't need to handle them
* here).
*
* <pre>
* nonFunctionLiteral
* : NULL
* | TRUE
* | FALSE
* | HEX_NUMBER
* | RATIONAL_NUMBER
* | DOUBLE_NUMBER
* | STRING
* | mapLiteral
* | arrayLiteral
* ;
* </pre>
*
* @return an expression matching the {@code literal} production above
*/
private DartExpression parseLiteral() {
beginLiteral();
if (PSEUDO_KEYWORDS_SET.contains(peek(0).getSyntax())) {
return done(parseIdentifier());
}
switch (peek(0)) {
case NULL_LITERAL: {
consume(Token.NULL_LITERAL);
return done(DartNullLiteral.get());
}
case TRUE_LITERAL: {
consume(Token.TRUE_LITERAL);
return done(DartBooleanLiteral.get(true));
}
case FALSE_LITERAL: {
consume(Token.FALSE_LITERAL);
return done(DartBooleanLiteral.get(false));
}
case INTEGER_LITERAL: {
consume(Token.INTEGER_LITERAL);
String number = ctx.getTokenString();
return done(DartIntegerLiteral.get(new BigInteger(number)));
}
case DOUBLE_LITERAL: {
consume(Token.DOUBLE_LITERAL);
String number = ctx.getTokenString();
return done(DartDoubleLiteral.get(Double.parseDouble(number)));
}
case HEX_LITERAL: {
consume(Token.HEX_LITERAL);
String number = ctx.getTokenString();
return done(DartIntegerLiteral.get(new BigInteger(number, 16)));
}
case LBRACE: {
return done(parseMapLiteral(false, null));
}
case INDEX: {
expect(peek(0));
return done(new DartArrayLiteral(false, null, new ArrayList<DartExpression>()));
}
case LBRACK: {
return done(parseArrayLiteral(false, null));
}
case VOID:
// For better error recovery / code completion in the IDE, treat "void" as an identifier
// here and let it get reported as a resolution error.
case IDENTIFIER: {
return done(parseIdentifier());
}
case SEMICOLON: {
// this is separate from the default case for better error recovery,
// leaving the semicolon for the caller to use for a statement boundary
// we have to advance to get the proper position, but we want to leave
// the semicolon
startLookahead();
next();
reportUnexpectedToken(position(), null, Token.SEMICOLON);
rollback();
return done(new DartSyntheticErrorExpression(""));
}
default: {
Token unexpected = peek(0);
String unexpectedString = ctx.getTokenString();
if (unexpectedString == null && unexpected != Token.EOS) {
unexpectedString = unexpected.getSyntax();
}
// Don't eat tokens that could be used to successfully terminate a non-terminal
// further up the stack.
Set<Token> terminals = collectTerminalAnnotations();
if (!looksLikeTopLevelKeyword() && !terminals.contains(unexpected)) {
next();
}
reportUnexpectedToken(position(), null, unexpected);
StringBuilder tokenStr = new StringBuilder();
if (unexpectedString != null) {
tokenStr.append(unexpectedString);
}
// TODO(jat): should we eat additional tokens here for error recovery?
return done(new DartSyntheticErrorExpression(tokenStr.toString()));
}
}
}
/**
* mapLiteralEntry
* : STRING ':' expression
* ;
*/
private DartMapLiteralEntry parseMapLiteralEntry() {
beginMapLiteralEntry();
// Parse the key.
DartExpression keyExpr = parseStringWithPasting();
if (keyExpr == null) {
return done(null);
}
// Parse the value.
DartExpression value;
if (expect(Token.COLON)) {
value = parseExpression();
} else {
value = doneWithoutConsuming(new DartSyntheticErrorExpression());
}
return done(new DartMapLiteralEntry(keyExpr, value));
}
private boolean looksLikeString() {
switch(peek(0)) {
case STRING:
case STRING_SEGMENT:
case STRING_EMBED_EXP_START:
return true;
}
return false;
}
/**
* <pre> mapLiteral : '{' (mapLiteralEntry (',' mapLiteralEntry)* ','?)? '}' ;
* </pre>
*/
@Terminals(tokens={Token.RBRACE, Token.COMMA})
private DartExpression parseMapLiteral(boolean isConst, List<DartTypeNode> typeArguments) {
beginMapLiteral();
boolean foundOpenBrace = expect(Token.LBRACE);
boolean save = setAllowFunctionExpression(true);
List<DartMapLiteralEntry> entries = new ArrayList<DartMapLiteralEntry>();
while (!match(Token.RBRACE) && !match(Token.EOS)) {
if (!looksLikeString()) {
ctx.advance();
reportError(position(), ParserErrorCode.EXPECTED_STRING_LITERAL_MAP_ENTRY_KEY);
if (peek(0) == Token.COMMA) {
// a common error is to put an empty entry in the list, allow it to
// recover.
continue;
} else {
break;
}
}
DartMapLiteralEntry entry = parseMapLiteralEntry();
if (entry != null) {
entries.add(entry);
}
Token nextToken = peek(0);
switch (nextToken) {
// Must keep in sync with @Terminals above
case COMMA:
consume(Token.COMMA);
break;
// Must keep in sync with @Terminals above
case RBRACE:
break;
default:
if (entry == null) {
Set<Token> terminals = collectTerminalAnnotations();
if (!terminals.contains(nextToken) && !looksLikeTopLevelKeyword()) {
if (entry == null) {
// Ensure the parser makes progress.
ctx.advance();
}
}
}
reportError(position(), ParserErrorCode.EXPECTED_COMMA_OR_RIGHT_BRACE);
break;
}
}
expectCloseBrace(foundOpenBrace);
setAllowFunctionExpression(save);
return done(new DartMapLiteral(isConst, typeArguments, entries));
}
/**
* // The array literal syntax doesn't allow elided elements, unlike
* // in ECMAScript.
*
* <pre>
* arrayLiteral
* : '[' expressionList? ']'
* ;
* </pre>
*/
@Terminals(tokens={Token.RBRACK, Token.COMMA})
private DartExpression parseArrayLiteral(boolean isConst, List<DartTypeNode> typeArguments) {
beginArrayLiteral();
expect(Token.LBRACK);
boolean save = setAllowFunctionExpression(true);
List<DartExpression> exprs = new ArrayList<DartExpression>();
while (!match(Token.RBRACK) && !EOS()) {
exprs.add(parseExpression());
// Must keep in sync with @Terminals above
if (!optional(Token.COMMA)) {
break;
}
}
// Must keep in sync with @Terminals above
expect(Token.RBRACK);
setAllowFunctionExpression(save);
return done(new DartArrayLiteral(isConst, typeArguments, exprs));
}
/**
* Parse a postfix expression.
*
* <pre>
* postfixExpression
* | assignableExpression postfixOperator
* : primary selector*
* ;
* </pre>
*
* @return an expression matching the {@code postfixExpression} production above
*/
private DartExpression parsePostfixExpression() {
beginPostfixExpression();
DartExpression receiver = doneWithoutConsuming(parsePrimaryExpression());
DartExpression result = receiver;
do {
receiver = result;
result = doneWithoutConsuming(parseSelectorExpression(receiver));
} while (receiver != result);
Token token = peek(0);
if (token.isCountOperator()) {
ensureAssignable(result);
consume(token);
int tokenOffset = ctx.getTokenLocation().getBegin();
result = doneWithoutConsuming(new DartUnaryExpression(token, tokenOffset, result, false));
}
return done(result);
}
/**
* <pre>
* typeParameters? (arrayLiteral | mapLiteral)
* </pre>
*
* @param isConst <code>true</code> if a CONST expression
*
*/
private DartExpression tryParseTypedCompoundLiteral(boolean isConst) {
beginLiteral();
List<DartTypeNode> typeArguments = parseTypeArgumentsOpt();
switch (peek(0)) {
case INDEX:
beginArrayLiteral();
consume(Token.INDEX);
return done(done(new DartArrayLiteral(isConst, typeArguments, new ArrayList<DartExpression>())));
case LBRACK:
return done(parseArrayLiteral(isConst, typeArguments));
case LBRACE:
return done(parseMapLiteral(isConst, typeArguments));
default:
if (typeArguments != null) {
rollback();
return null;
}
}
// Doesn't look like a typed compound literal and no tokens consumed.
return done(null);
}
private enum LastSeenNode {
NONE,
STRING,
EXPRESSION;
}
private class DartStringInterpolationBuilder {
private final List<DartStringLiteral> strings = new ArrayList<DartStringLiteral>();
private final List<DartExpression> expressions = new ArrayList<DartExpression>();
private LastSeenNode lastSeen = LastSeenNode.NONE;
DartStringInterpolationBuilder() {
}
void addString(DartStringLiteral string) {
if (lastSeen == LastSeenNode.STRING) {
expressions.add(new DartSyntheticErrorExpression());
}
strings.add(string);
lastSeen = LastSeenNode.STRING;
}
void addExpression(DartExpression expression) {
switch (lastSeen) {
case EXPRESSION:
case NONE:
strings.add(DartStringLiteral.get(""));
break;
default:
break;
}
expressions.add(expression);
lastSeen = LastSeenNode.EXPRESSION;
}
void addInterpolation(DartStringInterpolation interpolation) {
strings.addAll(interpolation.getStrings());
expressions.addAll(interpolation.getExpressions());
lastSeen = LastSeenNode.STRING;
}
DartStringInterpolation buildInterpolation() {
if (strings.size() == expressions.size()) {
strings.add(DartStringLiteral.get(""));
}
return new DartStringInterpolation(strings, expressions);
}
}
/**
* Instances of the class {@code StringInterpolationParseError} represent the detection of an
* error that needs to be handled in an enclosing context.
*/
private static class StringInterpolationParseError extends RuntimeException {
private static final long serialVersionUID = 1L;
public StringInterpolationParseError() {
super();
}
}
/**
* <pre>
* string-interpolation
* : (STRING_SEGMENT? embedded-exp?)* STRING_LAST_SEGMENT
*
* embedded-exp
* : STRING_EMBED_EXP_START expression STRING_EMBED_EXP_END
* </pre>
*/
private DartExpression parseStringInterpolation() {
// TODO(sigmund): generalize to parse string templates as well.
if (peek(0) == Token.STRING_LAST_SEGMENT) {
throw new InternalCompilerException("Invariant broken");
}
beginStringInterpolation();
DartStringInterpolationBuilder builder = new DartStringInterpolationBuilder();
boolean inString = true;
while (inString) { // Iterate until we find the last string segment.
switch (peek(0)) {
case STRING_SEGMENT: {
beginStringSegment();
consume(Token.STRING_SEGMENT);
builder.addString(done(DartStringLiteral.get(ctx.getTokenString())));
break;
}
case STRING_LAST_SEGMENT: {
beginStringSegment();
consume(Token.STRING_LAST_SEGMENT);
builder.addString(done(DartStringLiteral.get(ctx.getTokenString())));
inString = false;
break;
}
case STRING_EMBED_EXP_START: {
consume(Token.STRING_EMBED_EXP_START);
/*
* We check for ILLEGAL specifically here to give nicer error
* messages, and because the scanner doesn't generate a
* STRING_EMBED_EXP_END to match the START in the case of an ILLEGAL
* token.
*/
if (peek(0) == Token.ILLEGAL) {
reportError(position(), ParserErrorCode.UNEXPECTED_TOKEN_IN_STRING_INTERPOLATION,
next());
builder.addExpression(new DartSyntheticErrorExpression(""));
break;
} else {
try {
builder.addExpression(parseExpression());
} catch (StringInterpolationParseError exception) {
if (peek(0) == Token.STRING_LAST_SEGMENT) {
break;
}
throw new InternalCompilerException("Invalid expression found in string interpolation");
}
}
Token lookAhead = peek(0);
String lookAheadString = getPeekTokenValue(0);
if (!expect(Token.STRING_EMBED_EXP_END)) {
String errorText = null;
if (lookAheadString != null && lookAheadString.length() > 0) {
errorText = lookAheadString;
} else if (lookAhead.getSyntax() != null && lookAhead.getSyntax().length() > 0) {
errorText = lookAhead.getSyntax();
}
if (errorText != null) {
builder.addExpression(new DartSyntheticErrorExpression(errorText));
}
inString = !(Token.STRING_LAST_SEGMENT == lookAhead);
}
break;
}
case EOS: {
reportError(position(), ParserErrorCode.INCOMPLETE_STRING_LITERAL);
inString = false;
break;
}
default: {
String errorText = getPeekTokenValue(0) != null && getPeekTokenValue(0).length() > 0
? getPeekTokenValue(0) : null;
if(errorText != null) {
builder.addExpression(new DartSyntheticErrorExpression(getPeekTokenValue(0)));
}
reportError(position(), ParserErrorCode.UNEXPECTED_TOKEN_IN_STRING_INTERPOLATION,
next());
break;
}
}
}
return done(builder.buildInterpolation());
}
/**
* Parse a return type, giving an error if the .
*
* @return a return type or null if the current text is not a return type
*/
private DartTypeNode parseReturnType() {
if (peek(0) == Token.VOID) {
return parseVoidType();
} else {
return parseTypeAnnotation();
}
}
/**
* Check if the current text could be a return type, and advance past it if so. The current
* position is unchanged if it is not a return type.
*
* NOTE: if the grammar is changed for what constitutes an acceptable return type, this method
* must be updated to match {@link #parseReturnType()}/etc.
*
* @return true if current text could be a return type, false otherwise
*/
private boolean isReturnType() {
beginReturnType();
if (optional(Token.VOID)) {
done(null);
return true;
}
if (!optional(Token.IDENTIFIER)) {
rollback();
return false;
}
// handle prefixed identifiers
if (optional(Token.PERIOD)) {
if (!optional(Token.IDENTIFIER)) {
rollback();
return false;
}
}
// skip over type arguments if they are present
if (optional(Token.LT)) {
int count = 1;
while (count > 0) {
switch (next()) {
case EOS:
rollback();
return false;
case LT:
count++;
break;
case GT:
count--;
break;
case SHL:
count += 2;
break;
case SAR: // >>
count -= 2;
break;
case COMMA:
case IDENTIFIER:
// extends is a pseudokeyword, so shows up as IDENTIFIER
break;
default:
rollback();
return false;
}
}
if (count < 0) {
// if we had too many > (which can only be >> or >>>), can't be a return type
rollback();
return false;
}
}
done(null);
return true;
}
/**
* Checks to see if the current text looks like a function expression:
*
* <pre>
* FUNCTION name? ( args ) < => | { >
* returnType name? ( args ) < => | { >
* name? ( args ) < => | { >
* </pre>
*
* The current position is unchanged on return.
*
* NOTE: if the grammar for function expressions changes, this method must be
* adapted to match the actual parsing code. It is acceptable for this method
* to return true when the source text does not actually represent a function
* expression (which would result in error messages assuming it was a function
* expression, but it must not do so when the source text would be correct if
* parsed as a non-function expression.
*
* @return true if the current text looks like a function expression, false
* otherwise
*/
@VisibleForTesting
boolean looksLikeFunctionExpression() {
if (!allowFunctionExpression) {
return false;
}
return looksLikeFunctionDeclarationOrExpression();
}
/**
* Check to see if the following tokens could be a function expression, and if so try and parse
* it as one.
*
* @return a function expression if found, or null (with no tokens consumed) if not
*/
private DartExpression parseFunctionExpressionWithReturnType() {
beginFunctionLiteral();
DartIdentifier[] namePtr = new DartIdentifier[1];
DartFunction function = parseFunctionDeclarationOrExpression(namePtr, false);
if (function == null) {
rollback();
return null;
}
if (function != null && function.getReturnTypeNode() != null) {
reportError(function.getReturnTypeNode(), ParserErrorCode.DEPRECATED_FUNCTION_LITERAL);
} else if (namePtr[0] != null) {
reportError(namePtr[0], ParserErrorCode.DEPRECATED_FUNCTION_LITERAL);
}
return done(new DartFunctionExpression(namePtr[0], doneWithoutConsuming(function), false));
}
/**
* Parse a function declaration or expression, including the body.
* <pre>
* ... | functionDeclaration functionBody
*
* functionDeclaration
* : returnType? identifier formalParameterList
* ;
*
* functionExpression
* : (returnType? identifier)? formalParameterList functionExpressionBody
* ;
*
* functionBody
* : '=>' expression ';'
* | block
* ;
*
* functionExpressionBody
* : '=>' expression
* | block
* ;
* </pre>
*
* @param namePtr out parameter - parsed function name stored in namePtr[0]
* @param isDeclaration true if this is a declaration (i.e. a name is required and a trailing
* semicolon is needed for arrow syntax
* @return a {@link DartFunction} containing the body of the function, or null
* if the next tokens cannot be parsed as a function declaration or expression
*/
private DartFunction parseFunctionDeclarationOrExpression(DartIdentifier[] namePtr,
boolean isDeclaration) {
DartTypeNode returnType = null;
namePtr[0] = null;
if (optionalPseudoKeyword(STATIC_KEYWORD)) {
reportError(position(), ParserErrorCode.LOCAL_CANNOT_BE_STATIC);
}
switch (peek(0)) {
case LPAREN:
// no type or name, just the formal parameter list
break;
case IDENTIFIER:
if (peek(1) == Token.LPAREN) {
// if there is only one identifier, it must be the name
namePtr[0] = parseIdentifier();
break;
}
//$FALL-THROUGH$
case VOID:
returnType = parseReturnType();
if (peek(0) == Token.IDENTIFIER) {
namePtr[0] = parseIdentifier();
}
break;
default:
return null;
}
FormalParameters params = parseFormalParameterList();
int parametersCloseParen = ctx.getTokenLocation().getBegin();
DartBlock body = parseFunctionStatementBody(true, isDeclaration);
DartFunction function = new DartFunction(params.val, params.openParen,
params.optionalOpenOffset, params.optionalCloseOffset, parametersCloseParen, body,
returnType);
doneWithoutConsuming(function);
return function;
}
/**
* Parse a primary expression.
*
* <pre>
* primary
* : THIS
* | SUPER assignableSelector
* | literal
* | identifier
* | NEW type ('.' identifier)? arguments
* | typeArguments? (arrayLiteral | mapLiteral)
* | CONST typeArguments? (arrayLiteral | mapLiteral)
* | CONST typeArguments? (arrayLiteral | mapLiteral)
* | CONST type ('.' identifier)? arguments
* | '(' expression ')'
* | string-interpolation
* | functionExpression
* ;
* </pre>
*
* @return an expression matching the {@code primary} production above
*/
private DartExpression parsePrimaryExpression() {
if (looksLikeFunctionExpression()) {
return parseFunctionExpressionWithReturnType();
}
switch (peek(0)) {
case THIS: {
beginThisExpression();
consume(Token.THIS);
return done(DartThisExpression.get());
}
case SUPER: {
beginSuperExpression();
consume(Token.SUPER);
return done(DartSuperExpression.get());
}
case NEW: {
beginNewExpression(); // DartNewExpression
consume(Token.NEW);
return done(parseConstructorInvocation(false));
}
case CONST: {
beginConstExpression();
consume(Token.CONST);
DartExpression literal = tryParseTypedCompoundLiteral(true);
if (literal != null) {
return done(literal);
}
return done(parseConstructorInvocation(true));
}
case LPAREN: {
beginParenthesizedExpression();
consume(Token.LPAREN);
beginExpression();
// inside parens, function blocks are allowed again
boolean save = setAllowFunctionExpression(true);
DartExpression expression = done(parseExpression());
setAllowFunctionExpression(save);
expectCloseParen();
return done(new DartParenthesizedExpression(expression));
}
case LT: {
beginLiteral();
DartExpression literal = tryParseTypedCompoundLiteral(false);
if (literal == null) {
reportError(position(), ParserErrorCode.EXPECTED_ARRAY_OR_MAP_LITERAL);
}
return done(literal);
}
case STRING:
case STRING_SEGMENT:
case STRING_EMBED_EXP_START: {
return parseStringWithPasting();
}
case STRING_LAST_SEGMENT:
throw new StringInterpolationParseError();
case CONDITIONAL:
return parseArgumentDefinitionTest();
default: {
return parseLiteral();
}
}
}
private DartExpression parseArgumentDefinitionTest() {
beginArgumentDefinitionTest();
int operatorOffset = position();
expect(Token.CONDITIONAL);
return done(new DartUnaryExpression(Token.CONDITIONAL, operatorOffset, parseIdentifier(), true));
}
private DartExpression parseConstructorInvocation(boolean isConst) {
List<DartTypeNode> parts = new ArrayList<DartTypeNode>();
beginConstructor();
do {
beginConstructorNamePart();
parts.add(done(new DartTypeNode(parseIdentifier(), parseTypeArgumentsOpt())));
} while (optional(Token.PERIOD));
assert parts.size() > 0;
DartNode constructor;
switch (parts.size()) {
case 1:
constructor = doneWithoutConsuming(parts.get(0));
break;
case 2: {
// This case is ambiguous. It can either be prefix.Type or
// Type.namedConstructor.
boolean hasPrefix = false;
DartTypeNode part1 = parts.get(0);
DartTypeNode part2 = parts.get(1);
if (prefixes.contains(((DartIdentifier) part1.getIdentifier()).getName())) {
hasPrefix = true;
}
if (!part2.getTypeArguments().isEmpty()) {
// If the second part has type arguments, the first part must be a prefix.
// If it isn't a prefix, the resolver will complain.
hasPrefix = true;
}
if (hasPrefix) {
constructor = doneWithoutConsuming(toPrefixedType(parts));
} else {
// Named constructor.
DartIdentifier identifier = (DartIdentifier) part2.getIdentifier();
constructor = doneWithoutConsuming(new DartPropertyAccess(part1, identifier));
}
break;
}
default: {
// This case is unambiguous. It must be prefix.Type.namedConstructor.
if (parts.size() > 3) {
reportError(parts.get(3), ParserErrorCode.EXPECTED_LEFT_PAREN);
}
DartTypeNode typeNode = doneWithoutConsuming(toPrefixedType(parts));
DartIdentifier identifier = (DartIdentifier)parts.get(2).getIdentifier();
constructor = doneWithoutConsuming(new DartPropertyAccess(typeNode, identifier));
break;
}
}
boolean save = setAllowFunctionExpression(true);
try {
List<DartExpression> args = parseArguments();
return done(new DartNewExpression(constructor, args, isConst));
} finally {
setAllowFunctionExpression(save);
}
}
private DartTypeNode toPrefixedType(List<DartTypeNode> parts) {
DartIdentifier part1 = (DartIdentifier)parts.get(0).getIdentifier();
DartTypeNode part2 = parts.get(1);
DartIdentifier identifier = (DartIdentifier) part2.getIdentifier();
DartPropertyAccess access = doneWithoutConsuming(new DartPropertyAccess(part1, identifier));
return new DartTypeNode(access, part2.getTypeArguments());
}
/**
* Parse a selector expression.
*
* <pre>
* selector
* : assignableSelector
* | arguments
* ;
* </pre>
*
* @return an expression matching the {@code selector} production above
*/
private DartExpression parseSelectorExpression(DartExpression receiver) {
DartExpression expression = tryParseAssignableSelector(receiver);
if (expression != null) {
return expression;
}
if (peek(0) == Token.LPAREN) {
beginSelectorExpression();
boolean save = setAllowFunctionExpression(true);
List<DartExpression> args = parseArguments();
setAllowFunctionExpression(save);
if (receiver instanceof DartIdentifier) {
return(done(new DartUnqualifiedInvocation((DartIdentifier) receiver, args)));
} else {
return(done(new DartFunctionObjectInvocation(receiver, args)));
}
}
return receiver;
}
/**
* <pre>
* assignableSelector
* : '[' expression ']'
* | '.' identifier
* ;
* </pre>
*/
private DartExpression tryParseAssignableSelector(DartExpression receiver) {
switch (peek(0)) {
case PERIOD:
consume(Token.PERIOD);
switch (peek(0)) {
case SEMICOLON:
case RBRACE:
reportError(position(), ParserErrorCode.EXPECTED_IDENTIFIER);
DartIdentifier error = doneWithoutConsuming(new DartIdentifier(""));
return doneWithoutConsuming(new DartPropertyAccess(receiver, error));
}
// receiver.() = missing name
if (peek(0) == Token.LPAREN) {
reportUnexpectedToken(position(), Token.IDENTIFIER, peek(0));
DartIdentifier name;
{
beginIdentifier();
name = done(new DartSyntheticErrorIdentifier());
}
boolean save = setAllowFunctionExpression(true);
DartMethodInvocation expr = doneWithoutConsuming(new DartMethodInvocation(receiver,
false, name, parseArguments()));
setAllowFunctionExpression(save);
return expr;
}
// expect name
DartIdentifier name = parseIdentifier();
if (peek(0) == Token.LPAREN) {
boolean save = setAllowFunctionExpression(true);
DartMethodInvocation expr = doneWithoutConsuming(new DartMethodInvocation(receiver, false,
name, parseArguments()));
setAllowFunctionExpression(save);
return expr;
} else {
return doneWithoutConsuming(new DartPropertyAccess(receiver, name));
}
case LBRACK:
consume(Token.LBRACK);
DartExpression key = parseExpression();
expect(Token.RBRACK);
return doneWithoutConsuming(new DartArrayAccess(receiver, key));
default:
return null;
}
}
/**
* <pre>
* block
* : '{' statements deadCode* '}'
* ;
*
* statements
* : statement*
* ;
*
* deadCode
* : (normalCompletingStatement | abruptCompletingStatement)
* ;
* </pre>
*/
@Terminals(tokens={Token.RBRACE})
private DartBlock parseBlock() {
if (isDietParse) {
expect(Token.LBRACE);
DartBlock emptyBlock = new DartBlock(new ArrayList<DartStatement>());
int nesting = 1;
while (nesting > 0) {
Token token = next();
switch (token) {
case LBRACE:
++nesting;
break;
case RBRACE:
--nesting;
break;
case EOS:
return emptyBlock;
}
}
// Return an empty block so we don't generate unparseable code.
return emptyBlock;
} else {
Token nextToken = peek(0);
if (!nextToken.equals(Token.LBRACE)
&& (looksLikeTopLevelKeyword() || nextToken.equals(Token.RBRACE))) {
beginBlock();
// Allow recovery back to the top level.
reportErrorWithoutAdvancing(ParserErrorCode.UNEXPECTED_TOKEN);
return done(new DartBlock(new ArrayList<DartStatement>()));
}
beginBlock();
List<DartStatement> statements = new ArrayList<DartStatement>();
boolean foundOpenBrace = expect(Token.LBRACE);
while (!match(Token.RBRACE) && !EOS()) {
if (looksLikeTopLevelKeyword()) {
reportErrorWithoutAdvancing(ParserErrorCode.UNEXPECTED_TOKEN);
break;
}
int startPosition = position();
DartStatement newStatement = parseStatement();
if (newStatement == null) {
break;
}
if (startPosition == position()) {
// The parser is not making progress.
Set<Token> terminals = this.collectTerminalAnnotations();
if (terminals.contains(peek(0))) {
// bail out of the block
break;
}
reportUnexpectedToken(position(), null, next());
}
statements.add(newStatement);
}
expectCloseBrace(foundOpenBrace);
return done(new DartBlock(statements));
}
}
/**
* Parse a function statement body.
*
* <pre>
* functionStatementBody
* : '=>' expression ';'
* | block
* </pre>
*
* @param requireSemicolonForArrow true if a semicolon is required after an arrow expression
* @return {@link DartBlock} instance containing function body
*/
private DartBlock parseFunctionStatementBody(boolean allowBody, boolean requireSemicolonForArrow) {
// A break inside a function body should have nothing to do with a loop in
// the code surrounding the definition.
boolean oldInLoopStatement = inLoopStatement;
boolean oldInCaseStatement = inCaseStatement;
inLoopStatement = inCaseStatement = false;
try {
DartBlock result;
if (isDietParse) {
result = dietParseFunctionStatementBody();
} else {
beginFunctionStatementBody();
if (optional(Token.SEMICOLON)) {
if (allowBody) {
reportError(position(), ParserErrorCode.EXPECTED_FUNCTION_STATEMENT_BODY);
}
result = done(null);
} else if (optional(Token.ARROW)) {
DartExpression expr = parseExpression();
if (expr == null) {
expr = new DartSyntheticErrorExpression();
}
if (requireSemicolonForArrow) {
expect(Token.SEMICOLON);
}
result = done(makeReturnBlock(expr));
} else {
result = done(parseBlock());
}
}
if (!allowBody && result != null) {
reportError(result, ParserErrorCode.EXTERNAL_METHOD_BODY);
}
return result;
} finally {
inLoopStatement = oldInLoopStatement;
inCaseStatement = oldInCaseStatement;
}
}
private DartBlock dietParseFunctionStatementBody() {
DartBlock emptyBlock = new DartBlock(new ArrayList<DartStatement>());
if (optional(Token.ARROW)) {
while (true) {
Token token = next();
if (token == Token.SEMICOLON) {
break;
}
}
} else {
if (!peek(0).equals(Token.LBRACE) && looksLikeTopLevelKeyword()) {
// Allow recovery back to the top level.
reportErrorWithoutAdvancing(ParserErrorCode.UNEXPECTED_TOKEN);
return done(emptyBlock);
}
expect(Token.LBRACE);
int nesting = 1;
while (nesting > 0) {
Token token = next();
switch (token) {
case LBRACE:
++nesting;
break;
case RBRACE:
--nesting;
break;
case EOS:
return emptyBlock;
}
}
}
// Return an empty block so we don't generate unparseable code.
return emptyBlock;
}
/**
* Create a block containing a single return statement.
*
* @param returnVal return value expression
* @return block containing a single return statement
*/
private DartBlock makeReturnBlock(DartExpression returnVal) {
return new DartReturnBlock(returnVal);
}
/**
* <pre>
* initializedVariableDeclaration
* : constVarOrType initializedIdentifierList
* ;
*
* initializedIdentifierList
* : initializedIdentifier (',' initializedIdentifier)*
* ;
*
* initializedIdentifier
* : IDENTIFIER ('=' assignmentExpression)?
* ;
* </pre>
*/
private List<DartVariable> parseInitializedVariableList() {
List<DartVariable> idents = new ArrayList<DartVariable>();
do {
beginVariableDeclaration();
List<DartAnnotation> metadata = parseMetadata();
DartIdentifier name = parseIdentifier();
DartExpression value = null;
if (isParsingInterface) {
expect(Token.ASSIGN);
value = parseExpression();
} else if (optional(Token.ASSIGN)) {
value = parseExpression();
}
DartVariable variable = done(new DartVariable(name, value));
setMetadata(variable, metadata);
idents.add(variable);
} while (optional(Token.COMMA));
return idents;
}
private DartAssertStatement parseAssertStatement() {
beginAssertStatement();
expect(Token.ASSERT);
expect(Token.LPAREN);
DartExpression condition = parseExpression();
expectCloseParen();
expectStatmentTerminator();
return done(new DartAssertStatement(condition));
}
/**
* <pre>
* abruptCompletingStatement
* : BREAK identifier? ';'
* | CONTINUE identifier? ';'
* | RETURN expression? ';'
* | THROW expression? ';'
* ;
* </pre>
*/
private DartBreakStatement parseBreakStatement() {
beginBreakStatement();
expect(Token.BREAK);
DartIdentifier label = null;
if (match(Token.IDENTIFIER)) {
label = parseIdentifier();
} else if (!inLoopStatement && !inCaseStatement) {
// The validation of matching of labels to break statements is done later.
reportErrorWithoutAdvancing(ParserErrorCode.BREAK_OUTSIDE_OF_LOOP);
}
expectStatmentTerminator();
return done(new DartBreakStatement(label));
}
private DartContinueStatement parseContinueStatement() {
beginContinueStatement();
expect(Token.CONTINUE);
DartIdentifier label = null;
if (!inLoopStatement && !inCaseStatement) {
reportErrorWithoutAdvancing(ParserErrorCode.CONTINUE_OUTSIDE_OF_LOOP);
}
if (peek(0) == Token.IDENTIFIER) {
label = parseIdentifier();
} else if (!inLoopStatement && inCaseStatement) {
reportErrorWithoutAdvancing(ParserErrorCode.CONTINUE_IN_CASE_MUST_HAVE_LABEL);
}
expectStatmentTerminator();
return done(new DartContinueStatement(label));
}
private DartReturnStatement parseReturnStatement() {
beginReturnStatement();
expect(Token.RETURN);
DartExpression value = null;
if (peek(0) != Token.SEMICOLON) {
value = parseExpression();
}
expectStatmentTerminator();
return done(new DartReturnStatement(value));
}
private DartThrowExpression parseThrowExpression(boolean allowCascade) {
beginThrowExpression();
expect(Token.THROW);
DartExpression exception = null;
if (peek(0) != Token.SEMICOLON && peek(0) != Token.RPAREN) {
if (allowCascade) {
exception = parseExpression();
} else {
exception = parseExpressionWithoutCascade();
}
}
return done(new DartThrowExpression(exception));
}
/**
* <pre>
* statement
* : label* nonLabelledStatement
* ;
*
* label
* : identifier ':'
* ;
* </pre>
*
* @return a {@link DartStatement}
*/
@VisibleForTesting
public DartStatement parseStatement() {
List<DartIdentifier> labels = new ArrayList<DartIdentifier>();
while (peek(0) == Token.IDENTIFIER && peek(1) == Token.COLON) {
beginLabel();
labels.add(parseIdentifier());
expect(Token.COLON);
}
List<DartAnnotation> metadata = parseMetadata();
DartStatement statement = parseNonLabelledStatement();
if (!metadata.isEmpty() && statement instanceof DartVariableStatement) {
DartVariableStatement variableStatement = (DartVariableStatement) statement;
if (!variableStatement.getVariables().isEmpty()) {
setMetadata(variableStatement.getVariables().get(0), metadata);
}
}
for (int i = labels.size() - 1; i >= 0; i--) {
statement = done(new DartLabel(labels.get(i), statement));
}
return statement;
}
private boolean isFunctionExpression(DartStatement statement) {
if (!(statement instanceof DartExprStmt)) {
return false;
}
DartExpression expression = ((DartExprStmt) statement).getExpression();
if (!(expression instanceof DartFunctionExpression)) {
return false;
}
return ((DartFunctionExpression) expression).getName() == null;
}
/**
* <pre>
* normalCompletingStatement
* : functionStatement
* | initializedVariableDeclaration ';'
* | simpleStatement
* ;
*
* functionStatement
* : typeOrFunction identifier formalParameterList block
* ;
* ;
*
* simpleStatement
* : ('{')=> block // Guard to break tie with map literal.
* | expression? ';'
* | tryStatement
* | ASSERT '(' conditionalExpression ')' ';'
* | abruptCompletingStatement
* ;
* </pre>
*/
// TODO(zundel): Possibly we could use Token.IDENTIFIER too, but it is used
// in so many places, it might make recovery worse rather than better.
@Terminals(tokens={Token.IF, Token.SWITCH, Token.WHILE, Token.DO, Token.FOR,
Token.VAR, Token.FINAL, Token.CONTINUE, Token.BREAK, Token.RETURN, Token.THROW,
Token.TRY, Token.SEMICOLON })
private DartStatement parseNonLabelledStatement() {
// Try to parse as function declaration.
if (looksLikeFunctionDeclarationOrExpression()) {
ctx.begin();
DartStatement functionDeclaration = parseFunctionDeclaration();
// If "null", then we tried to parse, but found that this is not function declaration.
// So, parsing was rolled back and we can try to parse it as expression.
if (functionDeclaration != null) {
if (!isFunctionExpression(functionDeclaration)) {
ctx.done(null);
return functionDeclaration;
}
ctx.rollback();
} else {
ctx.done(null);
}
}
// Check possible statement kind.
switch (peek(0)) {
case ASSERT:
return parseAssertStatement();
case IF:
return parseIfStatement();
case SWITCH:
return parseSwitchStatement();
case WHILE:
return parseWhileStatement();
case DO:
return parseDoWhileStatement();
case FOR:
return parseForStatement();
case VAR: {
beginVarDeclaration();
consume(Token.VAR);
List<DartVariable> vars = parseInitializedVariableList();
expectStatmentTerminator();
return done(new DartVariableStatement(vars, null));
}
case FINAL: {
beginFinalDeclaration();
consume(peek(0));
DartTypeNode type = null;
if (peek(1) == Token.IDENTIFIER || peek(1) == Token.LT || peek(1) == Token.PERIOD) {
// We know we have a type.
type = parseTypeAnnotation();
}
List<DartVariable> vars = parseInitializedVariableList();
expectStatmentTerminator();
return done(new DartVariableStatement(vars, type, Modifiers.NONE.makeFinal()));
}
case LBRACE:
Token token = peek(1);
if (token == Token.STRING || token == Token.STRING_SEGMENT || token == Token.STRING_EMBED_EXP_START) {
int offset = skipStringLiteral(1);
if (peek(offset) == Token.COLON) {
DartStatement statement = parseExpressionStatement();
if (statement instanceof DartExprStmt
&& ((DartExprStmt) statement).getExpression() instanceof DartMapLiteral) {
reportError(
((DartExprStmt) statement).getExpression(),
ParserErrorCode.NON_CONST_MAP_LITERAL_STATEMENT);
}
return statement;
}
}
return parseBlock();
case CONTINUE:
return parseContinueStatement();
case BREAK:
return parseBreakStatement();
case RETURN:
return parseReturnStatement();
case THROW:
return parseExpressionStatement();
case TRY:
return parseTryStatement();
case SEMICOLON:
beginEmptyStatement();
consume(Token.SEMICOLON);
return done(new DartEmptyStatement());
case CONST:
// Check to see whether this is a variable declaration. If not, then default to parsing an
// expression statement.
int offset = skipTypeName(1);
if (offset > 1 && (peek(offset) == Token.IDENTIFIER || (offset == 2
&& (peek(offset) == Token.ASSIGN || peek(offset) == Token.COMMA || peek(offset) == Token.SEMICOLON)))) {
boolean hasType = peek(offset) == Token.IDENTIFIER;
beginVariableDeclaration();
next();
DartTypeNode type = null;
if (hasType) {
type = parseTypeAnnotation();
}
List<DartVariable> vars = parseInitializedVariableList();
expect(Token.SEMICOLON);
return done(new DartVariableStatement(vars, type, Modifiers.NONE.makeConstant().makeFinal()));
}
break;
case IDENTIFIER:
// We have already eliminated function declarations earlier, so check for:
// a) variable declarations;
// b) beginning of function literal invocation.
if (peek(1) == Token.LT || peekMaybeAS(1) == Token.IDENTIFIER
|| (peek(1) == Token.PERIOD && peek(2) == Token.IDENTIFIER)) {
beginTypeFunctionOrVariable();
DartTypeNode type = tryTypeAnnotation();
if (type != null && peekMaybeAS(0) == Token.IDENTIFIER) {
List<DartVariable> vars = parseInitializedVariableList();
if (optional(Token.SEMICOLON)) {
return done(new DartVariableStatement(vars, type));
} else if (peek(0) == Token.LPAREN) {
// Probably a function object invocation.
rollback();
} else {
//reportError(position(), ParserErrorCode.EXPECTED_SEMICOLON);
expectStatmentTerminator();
return done(new DartVariableStatement(vars, type));
}
} else {
rollback();
}
}
break;
}
return parseExpressionStatement();
}
/**
* Check if succeeding tokens look like a function declaration - the parser state is unchanged
* upon return.
*
* See {@link #parseFunctionDeclaration()}.
*
* @return true if the following tokens should be parsed as a function definition
*/
private boolean looksLikeFunctionDeclarationOrExpression() {
beginMethodName();
try {
optionalPseudoKeyword(STATIC_KEYWORD);
if (peek(0) == Token.IDENTIFIER && peek(1) == Token.LPAREN) {
// just a name, no return type
consume(Token.IDENTIFIER);
} else if (isReturnType()) {
if (!optional(Token.IDENTIFIER)) {
// return types must be followed by a function name
return false;
}
}
// start of parameter list
if (!optional(Token.LPAREN)) {
return false;
}
// if it looks as "(Type name, ....)" then it may be function expression
boolean hasTwoIdentifiersComma;
{
int nameOffset = skipTypeName(0);
hasTwoIdentifiersComma = nameOffset != -1 && peek(nameOffset + 0) == Token.IDENTIFIER
&& peek(nameOffset + 1) == Token.COMMA;
}
// find matching parenthesis
int count = 1;
while (count != 0) {
switch (next()) {
case EOS:
return false;
case LPAREN:
count++;
break;
case RPAREN:
count--;
break;
}
}
return (peek(0) == Token.ARROW || peek(0) == Token.LBRACE) || hasTwoIdentifiersComma;
} finally {
rollback();
}
}
/**
* Parse a function declaration.
*
* <pre>
* nonLabelledStatement : ...
* | functionDeclaration functionBody
*
* functionDeclaration
* : FUNCTION identifier formalParameterList
* { legacy($start, "deprecated 'function' keyword"); }
* | returnType error=FUNCTION identifier? formalParameterList
* { legacy($error, "deprecated 'function' keyword"); }
* | returnType? identifier formalParameterList
* ;
* </pre>
*
* @return a {@link DartStatement} representing the function declaration or <code>null</code> if
* code ends with function invocation, so this is not function declaration.
*/
private DartStatement parseFunctionDeclaration() {
beginFunctionDeclaration();
DartIdentifier[] namePtr = new DartIdentifier[1];
DartFunction function = parseFunctionDeclarationOrExpression(namePtr, true);
if (function.getBody() instanceof DartReturnBlock || peek(0) != Token.LPAREN) {
return done(new DartExprStmt(doneWithoutConsuming(new DartFunctionExpression(namePtr[0],
doneWithoutConsuming(function),
true))));
} else {
rollback();
return null;
}
}
private DartStatement parseExpressionStatement() {
beginExpressionStatement();
DartExpression expression = parseExpression();
expectStatmentTerminator();
return done(new DartExprStmt(expression));
}
/**
* Expect a close paren, reporting an error and consuming tokens until a
* plausible continuation is found if it isn't present.
*/
private void expectCloseParen() {
int parenCount = 1;
Token nextToken = peek(0);
switch (nextToken) {
case RPAREN:
expect(Token.RPAREN);
return;
case EOS:
case LBRACE:
case SEMICOLON:
reportError(position(), ParserErrorCode.EXPECTED_TOKEN, Token.RPAREN.getSyntax(),
nextToken.getSyntax());
return;
case LPAREN:
++parenCount;
//$FALL-THROUGH$
default:
reportError(position(), ParserErrorCode.EXPECTED_TOKEN, Token.RPAREN.getSyntax(),
nextToken.getSyntax());
Set<Token> terminals = this.collectTerminalAnnotations();
if (terminals.contains(nextToken) || looksLikeTopLevelKeyword()) {
return;
}
break;
}
// eat tokens until we get a close paren or a plausible terminator (which
// is not consumed)
while (parenCount > 0) {
switch (peek(0)) {
case RPAREN:
expect(Token.RPAREN);
--parenCount;
break;
case LPAREN:
expect(Token.LPAREN);
++parenCount;
break;
case EOS:
reportErrorWithoutAdvancing(ParserErrorCode.UNEXPECTED_TOKEN);
return;
case LBRACE:
case SEMICOLON:
return;
default:
next();
break;
}
}
}
/**
* Expect a close brace, reporting an error and consuming tokens until a
* plausible continuation is found if it isn't present.
*/
private void expectCloseBrace(boolean foundOpenBrace) {
// If a top level keyword is seen, bail out to recover.
if (looksLikeTopLevelKeyword()) {
reportUnexpectedToken(position(), Token.RBRACE, peek(0));
return;
}
int braceCount = 0;
if (foundOpenBrace) {
braceCount++;
}
Token nextToken = peek(0);
if (expect(Token.RBRACE)) {
return;
}
if (nextToken == Token.LBRACE) {
braceCount++;
}
// eat tokens until we get a matching close brace or end of stream
while (braceCount > 0) {
if (looksLikeTopLevelKeyword()) {
return;
}
switch (next()) {
case RBRACE:
braceCount--;
break;
case LBRACE:
braceCount++;
break;
case EOS:
return;
}
}
}
/**
* Collect plausible statement tokens and return a synthetic error statement
* containing them.
* <p>
* Note that this is a crude heuristic that needs to be improved for better
* error recovery.
*
* @return a {@link DartSyntheticErrorStatement}
*/
private DartStatement parseErrorStatement() {
StringBuilder buf = new StringBuilder();
boolean done = false;
int braceCount = 1;
while (!done) {
buf.append(getPeekTokenValue(0));
next();
switch (peek(0)) {
case RBRACE:
if (--braceCount == 0) {
done = true;
}
break;
case LBRACE:
braceCount++;
break;
case EOS:
case SEMICOLON:
done = true;
break;
}
}
return new DartSyntheticErrorStatement(buf.toString());
}
/**
* Look for a statement terminator, giving error messages and consuming tokens
* for error recovery.
*/
protected void expectStatmentTerminator() {
Token token = peek(0);
if (expect(Token.SEMICOLON)) {
return;
}
Set<Token> terminals = collectTerminalAnnotations();
assert(terminals.contains(Token.SEMICOLON));
if (peek(0) == token) {
reportErrorWithoutAdvancing(ParserErrorCode.EXPECTED_SEMICOLON);
} else {
reportError(position(), ParserErrorCode.EXPECTED_SEMICOLON);
token = peek(0);
}
// Consume tokens until we see something that could terminate or start a new statement
while (token != Token.SEMICOLON) {
if (looksLikeTopLevelKeyword() || terminals.contains(token)) {
return;
}
token = next();
}
}
/**
* Report an error without advancing past the next token.
*
* @param errCode the error code to report, which may take a string parameter
* containing the actual token found
*/
private void reportErrorWithoutAdvancing(ErrorCode errCode) {
startLookahead();
Token actual = peek(0);
next();
reportError(position(), errCode, actual);
rollback();
}
/**
* <pre>
* iterationStatement
* : WHILE '(' expression ')' statement
* | DO statement WHILE '(' expression ')' ';'
* | FOR '(' forLoopParts ')' statement
* ;
* </pre>
*/
private DartWhileStatement parseWhileStatement() {
beginWhileStatement();
expect(Token.WHILE);
expect(Token.LPAREN);
DartExpression condition = parseExpression();
expectCloseParen();
int closeParenOffset = ctx.getTokenLocation().getBegin();
DartStatement body = parseLoopStatement();
return done(new DartWhileStatement(condition, closeParenOffset, body));
}
/**
* <pre>
* iterationStatement
* : WHILE '(' expression ')' statement
* | DO statement WHILE '(' expression ')' ';'
* | FOR '(' forLoopParts ')' statement
* ;
* </pre>
*/
private DartDoWhileStatement parseDoWhileStatement() {
beginDoStatement();
expect(Token.DO);
DartStatement body = parseLoopStatement();
expect(Token.WHILE);
expect(Token.LPAREN);
DartExpression condition = parseExpression();
expectCloseParen();
expectStatmentTerminator();
return done(new DartDoWhileStatement(condition, body));
}
/**
* Use this wrapper to parse the body of a loop
*
* Sets up flag variables to make sure continue and break are properly
* marked as errors when in wrong context.
*/
private DartStatement parseLoopStatement() {
boolean oldInLoop = inLoopStatement;
inLoopStatement = true;
try {
return parseStatement();
} finally {
inLoopStatement = oldInLoop;
}
}
/**
* <pre>
* iterationStatement
* : WHILE '(' expression ')' statement
* | DO statement WHILE '(' expression ')' ';'
* | FOR '(' forLoopParts ')' statement
* ;
*
* forLoopParts
* : forInitializerStatement expression? ';' expressionList?
* | constVarOrType? identifier IN expression
* ;
*
* forInitializerStatement
* : initializedVariableDeclaration ';'
* | expression? ';'
* ;
* </pre>
*/
private DartStatement parseForStatement() {
beginForStatement();
expect(Token.FOR);
expect(Token.LPAREN);
// Setup
DartStatement setup = null;
if (peek(0) != Token.SEMICOLON) {
// Found a setup expression/statement
beginForInitialization();
Modifiers modifiers = Modifiers.NONE;
if (optional(Token.VAR)) {
setup = done(new DartVariableStatement(parseInitializedVariableList(), null, modifiers));
} else {
if (optional(Token.CONST)) {
modifiers = modifiers.makeConstant();
}
if (optional(Token.FINAL)) {
modifiers = modifiers.makeFinal();
}
DartTypeNode type = (peek(1) == Token.IDENTIFIER || peek(1) == Token.LT || peek(1) == Token.PERIOD)
? tryTypeAnnotation() : null;
if (modifiers.isFinal() || type != null) {
setup = done(new DartVariableStatement(parseInitializedVariableList(), type, modifiers));
} else {
setup = done(new DartExprStmt(parseExpression()));
}
}
}
if (optional(Token.IN)) {
if (setup instanceof DartVariableStatement) {
DartVariableStatement variableStatement = (DartVariableStatement) setup;
List<DartVariable> variables = variableStatement.getVariables();
if (variables.size() != 1) {
reportError(variables.get(1), ParserErrorCode.FOR_IN_WITH_MULTIPLE_VARIABLES);
}
DartExpression initializer = variables.get(0).getValue();
if (initializer != null) {
reportError(initializer, ParserErrorCode.FOR_IN_WITH_VARIABLE_INITIALIZER);
}
} else {
DartExpression expression = ((DartExprStmt) setup).getExpression();
if (!(expression instanceof DartIdentifier)) {
reportError(setup, ParserErrorCode.FOR_IN_WITH_COMPLEX_VARIABLE);
}
}
DartExpression iterable = parseExpression();
expectCloseParen();
int closeParenOffset = ctx.getTokenLocation().getBegin();
DartStatement body = parseLoopStatement();
return done(new DartForInStatement(setup, iterable, closeParenOffset, body));
} else if (optional(Token.SEMICOLON)) {
// Condition
DartExpression condition = null;
if (peek(0) != Token.SEMICOLON) {
condition = parseExpression();
}
expect(Token.SEMICOLON);
// Next
DartExpression next = null;
if (peek(0) != Token.RPAREN) {
next = parseExpressionList();
}
expectCloseParen();
int closeParenOffset = ctx.getTokenLocation().getBegin();
DartStatement body = parseLoopStatement();
return done(new DartForStatement(setup, condition, next, closeParenOffset, body));
} else {
reportUnexpectedToken(position(), null, peek(0));
return done(parseErrorStatement());
}
}
/**
* <pre>
* selectionStatement
* : IF '(' expression ')' statement ((ELSE)=> ELSE statement)?
* | SWITCH '(' expression ')' '{' switchCase* defaultCase? '}'
* ;
* </pre>
*/
private DartIfStatement parseIfStatement() {
beginIfStatement();
expect(Token.IF);
expect(Token.LPAREN);
DartExpression condition = parseExpression();
expectCloseParen();
int closeParenOffset = ctx.getTokenLocation().getBegin();
DartStatement yes = parseStatement();
DartStatement no = null;
int elseTokenOffset = 0;
if (optional(Token.ELSE)) {
elseTokenOffset = ctx.getTokenLocation().getBegin();
no = parseStatement();
}
return done(new DartIfStatement(condition, closeParenOffset, yes, elseTokenOffset, no));
}
/**
* <pre>
* caseStatements
* : normalCompletingStatement* abruptCompletingStatement
* ;
* </pre>
*/
private List<DartStatement> parseCaseStatements() {
List<DartStatement> statements = new ArrayList<DartStatement>();
DartStatement statement = null;
boolean endOfCaseFound = false;
boolean warnedUnreachable = false;
while (true) {
switch (peek(0)) {
case CLASS:
// exit loop to report error condition
case CASE:
case DEFAULT:
case RBRACE:
case EOS:
return statements;
case IDENTIFIER:
// Handle consecutively labeled case statements
if (isCaseOrDefault()) {
return statements;
}
default:
boolean oldInCaseStatement = inCaseStatement;
inCaseStatement = true;
try {
if (endOfCaseFound && !warnedUnreachable) {
reportErrorWithoutAdvancing(ParserErrorCode.UNREACHABLE_CODE_IN_CASE);
warnedUnreachable = true;
}
statement = parseStatement();
} finally {
inCaseStatement = oldInCaseStatement;
}
if (statement == null) {
return statements;
}
// Don't add unreachable code to the list of statements.
if (!endOfCaseFound) {
statements.add(statement);
if (statement.isAbruptCompletingStatement()) {
endOfCaseFound = true;
}
}
}
}
}
private boolean isCaseOrDefault() {
int index = 0;
while (peek(index) == Token.IDENTIFIER && peek(index + 1) == Token.COLON) {
index += 2;
}
Token next = peek(index);
return next == Token.CASE || next == Token.DEFAULT;
}
/**
* <pre>
* switchCase
* : label? (CASE expression ':')+ caseStatements
* ;
* </pre>
*/
private DartSwitchMember parseCaseMember(List<DartLabel> labels) {
// The begin() associated with the done() in this method is in the method
// parseSwitchStatement(), called by beginSwitchMember().
expect(Token.CASE);
DartExpression caseExpr = parseExpression();
expect(Token.COLON);
return done(new DartCase(caseExpr, labels, parseCaseStatements()));
}
/**
* <pre>
* defaultCase
* : label? (CASE expression ':')* DEFAULT ':' caseStatements
* ;
* </pre>
*/
private DartSwitchMember parseDefaultMember(List<DartLabel> labels) {
// The begin() associated with the done() in this method is in the method
// parseSwitchStatement(), called by beginSwitchMember().
expect(Token.DEFAULT);
expect(Token.COLON);
return done(new DartDefault(labels, parseCaseStatements()));
}
/**
* <pre>
* selectionStatement
* : IF '(' expression ')' statement ((ELSE)=> ELSE statement)?
* | SWITCH '(' expression ')' '{' switchCase* defaultCase? '}'
* ;
* </pre>
*/
private DartStatement parseSwitchStatement() {
beginSwitchStatement();
expect(Token.SWITCH);
expect(Token.LPAREN);
DartExpression expr = parseExpression();
expectCloseParen();
List<DartSwitchMember> members = new ArrayList<DartSwitchMember>();
if (peek(0) != Token.LBRACE) {
reportUnexpectedToken(position(), Token.LBRACE, peek(0));
return done(new DartSwitchStatement(expr, members));
}
boolean foundOpenBrace = expect(Token.LBRACE);
boolean done = optional(Token.RBRACE);
while (!done) {
List<DartLabel> labels = new ArrayList<DartLabel>();
beginSwitchMember(); // switch member
while (peek(0) == Token.IDENTIFIER && peek(1) == Token.COLON) {
beginLabel();
DartIdentifier identifier = parseIdentifier();
expect(Token.COLON);
labels.add(done(new DartLabel(identifier, null)));
if (peek(0) == Token.RBRACE) {
reportError(position(), ParserErrorCode.LABEL_NOT_FOLLOWED_BY_CASE_OR_DEFAULT);
expectCloseBrace(foundOpenBrace);
return done(new DartSwitchStatement(expr, members));
}
}
if (peek(0) == Token.CASE) {
members.add(parseCaseMember(labels));
} else if (optional(Token.RBRACE)) {
if (!labels.isEmpty()) {
reportError(position(), ParserErrorCode.EXPECTED_CASE_OR_DEFAULT);
}
done = true;
done(null);
} else {
if (peek(0) == Token.DEFAULT) {
members.add(parseDefaultMember(labels));
}
expectCloseBrace(foundOpenBrace);
done = true; // Ensure termination.
}
}
return done(new DartSwitchStatement(expr, members));
}
/**
* <pre>
* catchParameter
* : FINAL type? identifier
* | VAR identifier
* | type identifier
* ;
* </pre>
*/
private DartParameter parseCatchParameter() {
beginCatchParameter();
List<DartAnnotation> metadata = parseMetadata();
DartTypeNode type = null;
Modifiers modifiers = Modifiers.NONE;
boolean isDeclared = false;
if (optional(Token.VAR)) {
isDeclared = true;
} else {
if (optional(Token.FINAL)) {
modifiers = modifiers.makeFinal();
isDeclared = true;
}
if (peek(1) != Token.COMMA && peek(1) != Token.RPAREN) {
type = parseTypeAnnotation();
isDeclared = true;
}
}
DartIdentifier name = parseIdentifier();
if (!isDeclared) {
reportError(name, ParserErrorCode.EXPECTED_VAR_FINAL_OR_TYPE);
}
DartParameter parameter = done(new DartParameter(name, type, null, null, modifiers));
setMetadata(parameter, metadata);
return parameter;
}
/**
* Parse either the old try statement syntax:
* <pre>
* tryStatement
* : TRY block (catchPart+ finallyPart? | finallyPart)
* ;
*
* catchPart
* : CATCH '(' declaredIdentifier (',' declaredIdentifier)? ')' block
* ;
*
* finallyPart
* : FINALLY block
* ;
* </pre>
* or the new syntax:
* <pre>
* tryStatement
* : TRY block (onPart+ finallyPart? | finallyPart)
* ;
*
* onPart
* : catchPart block
* | ON qualified catchPart? block
*
* catchPart
* : CATCH '(' identifier (',' identifier)? ')'
* ;
*
* finallyPart
* : FINALLY block
* ;
* </pre>
*/
private DartTryStatement parseTryStatement() {
beginTryStatement();
// Try.
expect(Token.TRY);
// TODO(zundel): It would be nice here to setup 'ON', 'CATCH' and 'FINALLY' as tokens for recovery
DartBlock tryBlock = parseBlock();
List<DartCatchBlock> catches = new ArrayList<DartCatchBlock>();
while (peekPseudoKeyword(0, ON_KEYWORD) || match(Token.CATCH)) {
// TODO(zundel): It would be nice here to setup 'FINALLY' as token for recovery
if (peekPseudoKeyword(0, ON_KEYWORD)) {
beginCatchClause();
next();
int onTokenOffset = position();
DartTypeNode exceptionType = parseTypeAnnotation();
DartParameter exception = null;
DartParameter stackTrace = null;
if (optional(Token.CATCH)) {
expect(Token.LPAREN);
beginCatchParameter();
List<DartAnnotation> metadata = parseMetadata();
DartIdentifier exceptionName = parseIdentifier();
exception = done(new DartParameter(exceptionName, exceptionType, null, null, Modifiers.NONE));
setMetadata(exception, metadata);
if (optional(Token.COMMA)) {
beginCatchParameter();
DartIdentifier stackName = parseIdentifier();
stackTrace = done(new DartParameter(stackName, null, null, null, Modifiers.NONE));
}
expectCloseParen();
} else {
// Create a dummy identifier that the user cannot reliably reference.
beginCatchParameter();
List<DartAnnotation> metadata = parseMetadata();
beginIdentifier();
DartIdentifier exceptionName = done(new DartIdentifier("e" + Long.toHexString(System.currentTimeMillis())));
exception = done(new DartParameter(exceptionName, exceptionType, null, null, Modifiers.NONE));
setMetadata(exception, metadata);
}
DartBlock block = parseBlock();
catches.add(done(new DartCatchBlock(block, onTokenOffset, exception, stackTrace)));
} else {
beginCatchClause();
next();
expect(Token.LPAREN);
DartParameter exception;
if (match(Token.IDENTIFIER) && (peek(1) == Token.COMMA || peek(1) == Token.RPAREN)) {
beginCatchParameter();
List<DartAnnotation> metadata = parseMetadata();
DartIdentifier exceptionName = parseIdentifier();
exception = done(new DartParameter(exceptionName, null , null, null, Modifiers.NONE));
setMetadata(exception, metadata);
} else {
// Old-style parameter
reportError(position(), ParserErrorCode.DEPRECATED_CATCH);
exception = parseCatchParameter();
}
DartParameter stackTrace = null;
if (optional(Token.COMMA)) {
if (match(Token.IDENTIFIER) && peek(1) == Token.RPAREN) {
beginCatchParameter();
List<DartAnnotation> metadata = parseMetadata();
DartIdentifier stackName = parseIdentifier();
stackTrace = done(new DartParameter(stackName, null, null, null, Modifiers.NONE));
setMetadata(stackTrace, metadata);
} else {
// Old-style parameter
reportError(position(), ParserErrorCode.DEPRECATED_CATCH);
stackTrace = parseCatchParameter();
}
}
expectCloseParen();
DartBlock block = parseBlock();
catches.add(done(new DartCatchBlock(block, -1, exception, stackTrace)));
}
}
// Finally.
DartBlock finallyBlock = null;
if (optional(Token.FINALLY)) {
finallyBlock = parseBlock();
}
if ( catches.size() == 0 && finallyBlock == null) {
reportError(new DartCompilationError(tryBlock.getSourceInfo().getSource(), new Location(position()),
ParserErrorCode.CATCH_OR_FINALLY_EXPECTED));
}
return done(new DartTryStatement(tryBlock, catches, finallyBlock));
}
/**
* <pre>
* unaryExpression
* : postfixExpression
* | prefixOperator unaryExpression
* | incrementOperator assignableExpression
* ;
*
* @return an expression or null if noFail is true and the next tokens could not be parsed as an
* expression, leaving the state unchanged.
* </pre>
*/
private DartExpression parseUnaryExpression() {
// There is no unary plus operator in Dart.
// However, we allow a leading plus in decimal numeric literals.
if (optional(Token.ADD)) {
if (peek(0) != Token.INTEGER_LITERAL && peek(0) != Token.DOUBLE_LITERAL) {
reportError(position(), ParserErrorCode.NO_UNARY_PLUS_OPERATOR);
} else if (position() + 1 != peekTokenLocation(0).getBegin()) {
reportError(position(), ParserErrorCode.NO_SPACE_AFTER_PLUS);
}
}
// Check for unary minus operator.
Token token = peek(0);
if (token.isUnaryOperator() || token == Token.SUB) {
if (token == Token.DEC && peek(1) == Token.SUPER) {
beginUnaryExpression();
beginUnaryExpression();
consume(token);
int tokenOffset = ctx.getTokenLocation().getBegin();
DartExpression unary = parseUnaryExpression();
DartUnaryExpression unary2 = new DartUnaryExpression(Token.SUB, tokenOffset, unary, true);
return done(new DartUnaryExpression(Token.SUB, tokenOffset, done(unary2), true));
} else {
beginUnaryExpression();
consume(token);
int tokenOffset = ctx.getTokenLocation().getBegin();
DartExpression unary = parseUnaryExpression();
if (token.isCountOperator()) {
ensureAssignable(unary);
}
return done(new DartUnaryExpression(token, tokenOffset, unary, true));
}
} else {
return parsePostfixExpression();
}
}
/**
* <pre>
* type
* : qualified typeArguments?
* ;
* </pre>
*/
private DartTypeNode parseTypeAnnotation() {
beginTypeAnnotation();
return done(new DartTypeNode(parseQualified(false), parseTypeArgumentsOpt()));
}
/**
* <pre>
* type
* : qualified typeArguments? ('.' identifier)?
* ;
* </pre>
*/
private DartTypeNode parseTypeAnnotationPossiblyFollowedByName() {
beginTypeAnnotation();
boolean canBeFollowedByPeriod = true;
if (peek(Token.IDENTIFIER, Token.LT) || peek(Token.IDENTIFIER, Token.PERIOD, Token.IDENTIFIER, Token.LT)) {
canBeFollowedByPeriod = false;
}
return done(new DartTypeNode(parseQualified(canBeFollowedByPeriod), parseTypeArgumentsOpt()));
}
private boolean peek(Token... tokens) {
int index = 0;
for (Token token : tokens) {
if (peek(index++) != token) {
return false;
}
}
return true;
}
/**
* <pre>
* typeArguments
* : '<' typeList '>'
* ;
*
* typeList
* : type (',' type)*
* ;
* </pre>
*/
@Terminals(tokens={Token.GT, Token.COMMA})
private List<DartTypeNode> parseTypeArguments() {
consume(Token.LT);
List<DartTypeNode> arguments = new ArrayList<DartTypeNode>();
do {
arguments.add(parseTypeAnnotation());
} while (optional(Token.COMMA));
if (!tryParameterizedTypeEnd()) {
expect(Token.GT);
}
return arguments;
}
/**
* <pre>
* typeArguments?
* </pre>
*/
private List<DartTypeNode> parseTypeArgumentsOpt() {
return (peek(0) == Token.LT)
? parseTypeArguments()
: Collections.<DartTypeNode>emptyList();
}
/**
* <pre>
* qualified
* : identifier ('.' identifier)?
* ;
* </pre>
*/
private DartExpression parseQualified(boolean canBeFollowedByPeriod) {
beginQualifiedIdentifier();
DartIdentifier identifier = parseIdentifier();
if (!prefixes.contains(identifier.getName())) {
if (canBeFollowedByPeriod && !(peek(0) == Token.PERIOD && peek(1) == Token.IDENTIFIER && peek(2) == Token.PERIOD)) {
return done(identifier);
}
}
DartExpression qualified = identifier;
if (optional(Token.PERIOD)) {
// The previous identifier was a prefix.
qualified = new DartPropertyAccess(qualified, parseIdentifier());
}
return done(qualified);
}
private boolean tryParameterizedTypeEnd() {
switch (peek(0)) {
case GT:
consume(Token.GT);
return true;
case SAR:
setPeek(0, Token.GT);
return true;
default:
return false;
}
}
private DartTypeNode tryTypeAnnotation() {
if (peek(0) != Token.IDENTIFIER) {
return null;
}
List<DartTypeNode> typeArguments = new ArrayList<DartTypeNode>();
beginTypeAnnotation(); // to allow roll-back in case we're not at a type
DartNode qualified = parseQualified(false);
if (optional(Token.LT)) {
if (peek(0) != Token.IDENTIFIER) {
rollback();
return null;
}
beginTypeArguments();
DartNode qualified2 = parseQualified(false);
DartTypeNode argument;
switch (peek(0)) {
case LT:
// qualified < qualified2 <
argument = done(new DartTypeNode(qualified2, parseTypeArguments()));
break;
case GT:
case SAR:
// qualified < qualified2 >
case COMMA:
// qualified < qualified2 ,
argument = done(new DartTypeNode(qualified2, Collections.<DartTypeNode>emptyList()));
break;
default:
done(null);
rollback();
return null;
}
typeArguments.add(argument);
while (optional(Token.COMMA)) {
typeArguments.add(parseTypeAnnotation());
}
if (!tryParameterizedTypeEnd()) {
expect(Token.GT);
}
}
return done(new DartTypeNode(qualified, typeArguments));
}
private DartIdentifier parseIdentifier() {
beginIdentifier();
if (looksLikeTopLevelKeyword()) {
reportErrorWithoutAdvancing(ParserErrorCode.EXPECTED_IDENTIFIER);
return done(new DartSyntheticErrorIdentifier());
}
DartIdentifier identifier;
if (expect(Token.IDENTIFIER) && ctx.getTokenString() != null) {
identifier = new DartIdentifier(new String(ctx.getTokenString()));
} else {
identifier = new DartSyntheticErrorIdentifier();
}
return done(identifier);
}
public DartExpression parseEntryPoint() {
beginEntryPoint();
DartExpression entry = parseIdentifier();
while (!EOS()) {
expect(Token.PERIOD);
entry = doneWithoutConsuming(new DartPropertyAccess(entry, parseIdentifier()));
}
return done(entry);
}
private void ensureAssignable(DartExpression expression) {
if (expression != null && !expression.isAssignable()) {
reportError(position(), ParserErrorCode.ILLEGAL_ASSIGNMENT_TO_NON_ASSIGNABLE);
}
}
/**
* Increment the number of errors encountered while parsing this compilation unit. Returns whether
* the current error should be reported.
*
* @return whether the current error should be reported
*/
private boolean incErrorCount(ErrorCode errorCode) {
// count only errors, but not warnings (such as "abstract")
if (errorCode.getErrorSeverity() == ErrorSeverity.ERROR) {
errorCount++;
}
if (errorCount >= MAX_DEFAULT_ERRORS) {
if (errorCount == MAX_DEFAULT_ERRORS) {
// Create a 'too many errors' error.
DartCompilationError dartError = new DartCompilationError(ctx.getSource(),
ctx.getTokenLocation(), ParserErrorCode.NO_SOUP_FOR_YOU);
ctx.error(dartError);
}
// Consume the rest of the input stream. Throwing an exception - as suggested elsewhere in
// this file - is not ideal.
Token next = next();
while (next != null && next != Token.EOS) {
next = next();
}
}
return errorCount < MAX_DEFAULT_ERRORS;
}
private void reportDeprecatedError(int position, ErrorCode errorCode) {
// TODO(scheglov) remove after http://code.google.com/p/dart/issues/detail?id=6508
if (
true
&& !Elements.isCoreLibrarySource(source)
&& !Elements.isLibrarySource(source, "/isolate/isolate.dart")
&& !Elements.isLibrarySource(source, "/json/json.dart")
&& !Elements.isLibrarySource(source, "/math/math.dart")
&& !Elements.isLibrarySource(source, "/io/io.dart")
&& !Elements.isLibrarySource(source, "/crypto/crypto.dart")
&& !Elements.isLibrarySource(source, "/uri/uri.dart")
&& !Elements.isLibrarySource(source, "/utf/utf.dart")
&& !Elements.isLibrarySource(source, "/typed_data/typed_data.dart")
) {
super.reportError(position, errorCode);
}
}
@Override
protected void reportError(int position, ErrorCode errorCode, Object... arguments) {
// TODO(devoncarew): we're not correctly identifying dart:html as a core library
if (incErrorCount(errorCode)) {
super.reportError(position, errorCode, arguments);
}
}
@Override
protected void reportErrorAtPosition(int startPosition, int endPosition,
ErrorCode errorCode, Object... arguments) {
if (incErrorCount(errorCode)) {
super.reportErrorAtPosition(startPosition, endPosition, errorCode, arguments);
}
}
private void reportError(DartCompilationError dartError) {
if (incErrorCount(dartError.getErrorCode())) {
ctx.error(dartError);
errorHistory.add(dartError.hashCode());
}
}
private void reportError(DartNode node, ErrorCode errorCode, Object... arguments) {
if (node != null) {
reportError(new DartCompilationError(node, errorCode, arguments));
}
}
private boolean currentlyParsingToplevel() {
return !(isParsingInterface || isTopLevelAbstract || isParsingClass);
}
/**
* @return <code>true</code> if current token is built-in identifier which can have special
* meaning. For example if it is used as import prefix, this is not special meaning, this is just
* normal identifier.
*/
private boolean isBuiltInSpecial() {
Token nextToken = peek(1);
if (nextToken == Token.LT) {
return peek(2) != Token.IDENTIFIER;
}
return nextToken != Token.PERIOD && nextToken != Token.LPAREN;
}
}