| // 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.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.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.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()); |
| } |
| |
| 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); |
| |
| // parse any directives at the beginning of the source |
| parseDirectives(unit); |
| |
| while (!EOS()) { |
| DartNodeWithMetadata node = null; |
| beginTopLevelElement(); |
| List<DartAnnotation> metadata = parseMetadata(); |
| isParsingClass = isParsingInterface = false; |
| // Check for ABSTRACT_KEYWORD. |
| isTopLevelAbstract = false; |
| topLevelAbstractModifierPosition = 0; |
| if (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; |
| // TODO(scheglov) remove after http://code.google.com/p/dart/issues/detail?id=6318 |
| if (!Elements.isCoreLibrarySource(source) |
| && !Elements.isLibrarySource(source, "/isolate/isolate.dart") |
| && !Elements.isLibrarySource(source, "crypto/crypto.dart") |
| && !Elements.isDart2JsLibrarySource(source)) { |
| reportError(position(), ParserErrorCode.DEPRECATED_INTERFACE); |
| } |
| node = done(parseClass()); |
| } else if (peekPseudoKeyword(0, TYPEDEF_KEYWORD) |
| && (peek(1).equals(Token.IDENTIFIER) || peek(1).equals(Token.VOID) || peek(1).equals(Token.AS))) { |
| consume(Token.IDENTIFIER); |
| node = done(parseFunctionTypeAlias()); |
| } else if (looksLikeDirective()) { |
| reportErrorWithoutAdvancing(ParserErrorCode.DIRECTIVE_OUT_OF_ORDER); |
| parseDirectives(unit); |
| } else { |
| node = done(parseFieldOrMethod(false)); |
| } |
| // Parsing was successful, add node. |
| if (node != null) { |
| setMetadata(node, metadata); |
| unit.getTopLevelNodes().add(node); |
| // 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 (annotations != null && !annotations.isEmpty()) { |
| node.setMetadata(annotations); |
| if (node instanceof DartDeclaration<?>) { |
| 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")) { |
| DartDeclaration<?> declaration = (DartDeclaration<?>) node; |
| declaration.setObsoleteMetadata(declaration.getObsoleteMetadata().makeDeprecated()); |
| } else if (name.equals("override")) { |
| DartDeclaration<?> declaration = (DartDeclaration<?>) node; |
| declaration.setObsoleteMetadata(declaration.getObsoleteMetadata().makeOverride()); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private boolean looksLikeDirective() { |
| 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); |
| if (peekPseudoKeyword(0, LIBRARY_KEYWORD)) { |
| DartLibraryDirective libraryDirective = parseLibraryDirective(); |
| libUnit.setName(libraryDirective.getLibraryName()); |
| } |
| 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); |
| } |
| } |
| 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); |
| } |
| } |
| // |
| // 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()); |
| } |
| 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); |
| } |
| while (peek(0) == Token.SOURCE) { |
| beginSourceDirective(); |
| DartSourceDirective sourceDirective = done(parseSourceDirective()); |
| LibraryNode sourcePath = new LibraryNode(sourceDirective.getSourceUri().getValue()); |
| sourcePath.setSourceInfo(sourceDirective.getSourceInfo()); |
| libUnit.addSourcePath(sourcePath); |
| } |
| while (peek(0) == Token.RESOURCE) { |
| parseResourceDirective(); |
| } |
| while (peek(0) == Token.NATIVE) { |
| beginNativeDirective(); |
| DartNativeDirective nativeDirective = done(parseNativeDirective()); |
| LibraryNode nativePath = new LibraryNode(nativeDirective.getNativeUri().getValue()); |
| nativePath.setSourceInfo(nativeDirective.getSourceInfo()); |
| libUnit.addNativePath(nativePath); |
| } |
| |
| // add ourselves to the list of sources, so inline dart code will be parsed |
| libUnit.addSourcePath(libUnit.getSelfSourcePath()); |
| return done(libUnit); |
| } |
| |
| private void parseDirectives(DartUnit unit) { |
| List<DartAnnotation> metadata = parseMetadata(); |
| 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; |
| } |
| } |
| setMetadata(libraryDirective, metadata); |
| unit.getDirectives().add(libraryDirective); |
| } |
| while (peekPseudoKeyword(0, IMPORT_KEYWORD) || peekPseudoKeyword(0, EXPORT_KEYWORD)) { |
| if (peekPseudoKeyword(0, IMPORT_KEYWORD)) { |
| DartImportDirective importDirective = parseImportDirective(); |
| setMetadata(importDirective, metadata); |
| unit.getDirectives().add(importDirective); |
| } else { |
| DartExportDirective exportDirective = parseExportDirective(); |
| setMetadata(exportDirective, metadata); |
| unit.getDirectives().add(exportDirective); |
| } |
| } |
| while (peekPseudoKeyword(0, PART_KEYWORD)) { |
| if (peekPseudoKeyword(1, OF_KEYWORD)) { |
| DartPartOfDirective partOfDirective = parsePartOfDirective(); |
| setMetadata(partOfDirective, metadata); |
| unit.getDirectives().add(partOfDirective); |
| } else { |
| DartSourceDirective partDirective = parsePartDirective(); |
| setMetadata(partDirective, metadata); |
| unit.getDirectives().add(partDirective); |
| } |
| } |
| // |
| // 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; |
| } |
| } |
| setMetadata(libraryDirective, metadata); |
| unit.getDirectives().add(libraryDirective); |
| done(libraryDirective); |
| } |
| while (peek(0) == Token.IMPORT) { |
| beginImportDirective(); |
| DartImportDirective importDirective = parseObsoleteImportDirective(); |
| setMetadata(importDirective, metadata); |
| unit.getDirectives().add(done(importDirective)); |
| } |
| while (peek(0) == Token.SOURCE) { |
| beginSourceDirective(); |
| DartSourceDirective sourceDirective = parseSourceDirective(); |
| setMetadata(sourceDirective, metadata); |
| unit.getDirectives().add(done(sourceDirective)); |
| } |
| while (peek(0) == Token.RESOURCE) { |
| parseResourceDirective(); |
| } |
| while (peek(0) == Token.NATIVE) { |
| beginNativeDirective(); |
| DartNativeDirective nativeDirective = parseNativeDirective(); |
| setMetadata(nativeDirective, metadata); |
| unit.getDirectives().add(done(nativeDirective)); |
| } |
| } |
| |
| 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); |
| 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" |
| beginLiteral(); |
| expect(Token.STRING); |
| DartStringLiteral libUri = done(DartStringLiteral.get(ctx.getTokenString())); |
| |
| 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" |
| beginLiteral(); |
| expect(Token.STRING); |
| DartStringLiteral libUri = done(DartStringLiteral.get(ctx.getTokenString())); |
| |
| // allow "native" if we have "dart-ext:" import |
| if (StringUtils.startsWith(libUri.getValue(), "dart-ext:")) { |
| allowNativeKeyword = true; |
| } |
| |
| DartIdentifier prefix = null; |
| if (optional(Token.AS)) { |
| 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); |
| expect(Token.LPAREN); |
| |
| beginLiteral(); |
| expect(Token.STRING); |
| DartStringLiteral libUri = done(DartStringLiteral.get(ctx.getTokenString())); |
| |
| // 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); |
| expect(Token.LPAREN); |
| beginLiteral(); |
| expect(Token.STRING); |
| DartStringLiteral sourceUri = done(DartStringLiteral.get(ctx.getTokenString())); |
| 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); |
| beginLiteral(); |
| expect(Token.STRING); |
| @SuppressWarnings("unused") |
| DartStringLiteral resourceUri = done(DartStringLiteral.get(ctx.getTokenString())); |
| expectCloseParen(); |
| expect(Token.SEMICOLON); |
| } |
| |
| private DartNativeDirective parseNativeDirective() { |
| expect(Token.NATIVE); |
| expect(Token.LPAREN); |
| beginLiteral(); |
| expect(Token.STRING); |
| DartStringLiteral nativeUri = done(DartStringLiteral.get(ctx.getTokenString())); |
| 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 implementsOffset = -1; |
| List<DartTypeNode> interfaces = null; |
| if (isParsingInterface) { |
| if (optional(Token.EXTENDS)) { |
| interfaces = parseTypeAnnotationList(); |
| } |
| } else { |
| if (optional(Token.EXTENDS)) { |
| superType = parseTypeAnnotation(); |
| } |
| if (optionalPseudoKeyword(IMPLEMENTS_KEYWORD)) { |
| implementsOffset = position(); |
| interfaces = parseTypeAnnotationList(); |
| } |
| } |
| |
| // 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, superType, implementsOffset, |
| interfaces, defaultTokenOffset, openBraceOffset, closeBraceOffset, members, |
| typeParameters, defaultClass)); |
| } else { |
| return done(new DartClass(tokenOffset, tokenLength, name, nativeName, superType, |
| implementsOffset, interfaces, defaultTokenOffset, openBraceOffset, closeBraceOffset, |
| members, typeParameters, 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(0) == Token.AS) && peek(1) == Token.LPAREN) { |
| return true; |
| } |
| if ((peek(0) == Token.IDENTIFIER || peek(0) == Token.AS) && 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; |
| } |
| |
| /** |
| * <pre> |
| * functionTypeAlias |
| * : TYPEDEF functionPrefix typeParameters? |
| * formalParameterList ';' |
| * |
| * functionPrefix |
| * : returnType? identifier |
| * </pre> |
| */ |
| 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 (peek(1) != Token.LPAREN && optionalPseudoKeyword(EXTERNAL_KEYWORD)) { |
| modifiers = modifiers.makeExternal(); |
| } |
| if (peek(1) != Token.LPAREN && 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 (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 (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()) { |
| // TODO(scheglov) remove after http://code.google.com/p/dart/issues/detail?id=6322 |
| // TODO(scheglov) remove after http://code.google.com/p/dart/issues/detail?id=6323 |
| if (!Elements.isCoreLibrarySource(source) |
| && !Elements.isLibrarySource(source, "html/dartium/html_dartium.dart") |
| && !Elements.isLibrarySource(source, "/math/math.dart") |
| && !Elements.isLibrarySource(source, "/io/io_runtime.dart") |
| && !Elements.isLibrarySource(source, "/crypto/crypto.dart") |
| && !Elements.isLibrarySource(source, "/utf/utf.dart") |
| && !Elements.isDart2JsLibrarySource(source)) { |
| 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 && peek(nameIndex) != Token.AS)) { |
| // 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).equals(Token.IDENTIFIER)); |
| beginMethodName(); // begin() equivalent |
| try { |
| if (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.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.optionalCloseOffset, parametersCloseParen, parseNativeBlock(modifiers), null); |
| } else { |
| function = new DartFunction(formals.val, 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 (peek(1) != Token.LPAREN && 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); |
| if (peek(0) == Token.LPAREN) { |
| // TODO(scheglov) remove after http://code.google.com/p/dart/issues/detail?id=6297 |
| if (!Elements.isHtmlLibrarySource(source)) { |
| 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.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.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(); |
| List<DartAnnotation> fieldMetadata = parseMetadata(); |
| 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, fieldMetadata); |
| 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 optionalOpenOffset; |
| private final int optionalCloseOffset; |
| public FormalParameters(List<DartParameter> parameters, int optionalOpenOffset, |
| int optionalCloseOffset) { |
| this.val = parameters; |
| 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); |
| 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), 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 = peek(0).getPrecedence(); level >= precedence; level--) { |
| while (peek(0).getPrecedence() == level) { |
| int prevPositionStart = ctx.getTokenLocation().getBegin(); |
| int prevPositionEnd = ctx.getTokenLocation().getEnd(); |
| Token token = next(); |
| 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); |
| } |
| 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 (match(token)) { |
| reportError(position(), ParserErrorCode.INVALID_OPERATOR_CHAINING, |
| token.toString().toLowerCase()); |
| } |
| break; |
| } |
| } |
| } |
| done(null); |
| return result; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * 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; |
| } |
| 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.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(doneWithoutConsuming(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)); |
| } |
| 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 AS: |
| 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 || peek(1) == Token.IDENTIFIER |
| || (peek(1) == Token.PERIOD && peek(2) == Token.IDENTIFIER)) { |
| beginTypeFunctionOrVariable(); |
| DartTypeNode type = tryTypeAnnotation(); |
| if (type != null && peek(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.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 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) { |
| 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.EOS) { |
| 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(); |
| 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, 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, 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 && peek(0) != Token.AS) { |
| 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 && peek(0) != Token.AS) { |
| 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 (peek(0) == Token.AS) { |
| next(); |
| return done(new DartIdentifier("as")); |
| } |
| 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; |
| } |
| |
| @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); |
| } |
| } |