Index: editor/tools/plugins/com.google.dart.engine/src/com/google/dart/engine/parser/Parser.java |
=================================================================== |
--- editor/tools/plugins/com.google.dart.engine/src/com/google/dart/engine/parser/Parser.java (revision 15397) |
+++ editor/tools/plugins/com.google.dart.engine/src/com/google/dart/engine/parser/Parser.java (working copy) |
@@ -320,31 +320,7 @@ |
if (next == null) { |
return false; |
} |
- if (!matchesIdentifier(next)) { |
- return false; |
- } |
- next = next.getNext(); |
- if (matches(next, TokenType.LT)) { |
- // |
- // We should really skip over a list of type parameters, but that would require skipping |
- // arbitrary expressions (because of metadata), so we just look for the balancing '>'. |
- // |
- int depth = 1; |
- next = next.getNext(); |
- while (depth > 0 && !matches(next, TokenType.EOF)) { |
- if (matches(next, TokenType.LT)) { |
- depth++; |
- } else if (matches(next, TokenType.GT)) { |
- depth--; |
- } else if (matches(next, TokenType.GT_GT)) { |
- depth -= 2; |
- } else if (matches(next, TokenType.GT_GT_GT)) { |
- depth -= 3; |
- } |
- next = next.getNext(); |
- } |
- } |
- return next != null && matches(next, TokenType.OPEN_PAREN); |
+ return matchesIdentifier(next); |
} |
/** |
@@ -384,8 +360,7 @@ |
if (afterParameters == null) { |
return false; |
} |
- return matches(afterParameters, TokenType.OPEN_CURLY_BRACKET) |
- || matches(afterParameters, TokenType.FUNCTION); |
+ return matchesAny(afterParameters, TokenType.OPEN_CURLY_BRACKET, TokenType.FUNCTION); |
} |
/** |
@@ -464,6 +439,15 @@ |
return false; |
} |
+ /** |
+ * Compare the given tokens to find the token that appears first in the source being parsed. That |
+ * is, return the left-most of all of the tokens. The arguments are allowed to be {@code null}. |
+ * Return the token with the smallest offset, or {@code null} if there are no arguments or if all |
+ * of the arguments are {@code null}. |
+ * |
+ * @param tokens the tokens being compared |
+ * @return the token with the smallest offset |
+ */ |
private Token lexicallyFirst(Token... tokens) { |
Token first = null; |
int firstOffset = Integer.MAX_VALUE; |
@@ -562,6 +546,23 @@ |
} |
/** |
+ * Return {@code true} if the given token has any one of the given types. |
+ * |
+ * @param token the token being tested |
+ * @param types the types of token that are being tested for |
+ * @return {@code true} if the given token has any of the given types |
+ */ |
+ private boolean matchesAny(Token token, TokenType... types) { |
+ TokenType actualType = token.getType(); |
+ for (TokenType type : types) { |
+ if (actualType == type) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ /** |
* Return {@code true} if the current token is a valid identifier. Valid identifiers include |
* built-in identifiers (pseudo-keywords). |
* |
@@ -1064,13 +1065,12 @@ |
* </pre> |
* |
* @param commentAndMetadata the metadata to be associated with the member |
+ * @param abstractKeyword the token for the keyword 'abstract', or {@code null} if the keyword was |
+ * not given |
* @return the class declaration that was parsed |
*/ |
- private ClassDeclaration parseClassDeclaration(CommentAndMetadata commentAndMetadata) { |
- Token abstractKeyword = null; |
- if (matches(Keyword.ABSTRACT)) { |
- abstractKeyword = getAndAdvance(); |
- } |
+ private ClassDeclaration parseClassDeclaration(CommentAndMetadata commentAndMetadata, |
+ Token abstractKeyword) { |
Token keyword = expect(Keyword.CLASS); |
SimpleIdentifier name = parseSimpleIdentifier(ParserErrorCode.BUILT_IN_IDENTIFIER_AS_TYPE_NAME); |
String className = name.getName(); |
@@ -1095,7 +1095,10 @@ |
reportError(ParserErrorCode.UNEXPECTED_TOKEN, currentToken, currentToken.getLexeme()); |
advance(); |
} else { |
- members.add(parseClassMember(className)); |
+ ClassMember member = parseClassMember(className); |
+ if (member != null) { |
+ members.add(member); |
+ } |
} |
if (currentToken == memberStart) { |
reportError(ParserErrorCode.UNEXPECTED_TOKEN, currentToken, currentToken.getLexeme()); |
@@ -1132,7 +1135,7 @@ |
*/ |
private ClassMember parseClassMember(String className) { |
CommentAndMetadata commentAndMetadata = parseCommentAndMetadata(); |
- Modifiers modifiers = parseClassMemberModifiers(); |
+ Modifiers modifiers = parseModifiers(); |
if (matches(Keyword.VOID)) { |
TypeName returnType = parseReturnType(); |
if (matches(Keyword.GET) && matchesIdentifier(peek())) { |
@@ -1152,13 +1155,34 @@ |
} else if (matches(Keyword.OPERATOR) && peek().isOperator()) { |
validateModifiersForOperator(modifiers); |
return parseOperator(commentAndMetadata, modifiers.getExternalKeyword(), returnType); |
+ } else if (matchesIdentifier() |
+ && matchesAny( |
+ peek(), |
+ TokenType.OPEN_PAREN, |
+ TokenType.OPEN_CURLY_BRACKET, |
+ TokenType.FUNCTION)) { |
+ validateModifiersForGetterOrSetterOrMethod(modifiers); |
+ return parseMethodDeclaration( |
+ commentAndMetadata, |
+ modifiers.getExternalKeyword(), |
+ modifiers.getStaticKeyword(), |
+ returnType); |
+ } else { |
+ // We have found an error of some kind. Try to recover. |
+ if (matchesIdentifier()) { |
+ if (matchesAny(peek(), TokenType.EQ, TokenType.COMMA, TokenType.SEMICOLON)) { |
+ // We appear to have a variable declaration with a type of "void". |
+ reportError(ParserErrorCode.VOID_VARIABLE, returnType); |
+ return parseInitializedIdentifierList( |
+ commentAndMetadata, |
+ modifiers.getStaticKeyword(), |
+ validateModifiersForField(modifiers), |
+ returnType); |
+ } |
+ } |
+ // TODO(brianwilkerson) Report this error. |
+ return null; |
} |
- validateModifiersForGetterOrSetterOrMethod(modifiers); |
- return parseMethodDeclaration( |
- commentAndMetadata, |
- modifiers.getExternalKeyword(), |
- modifiers.getStaticKeyword(), |
- returnType); |
} else if (matches(Keyword.GET) && matchesIdentifier(peek())) { |
validateModifiersForGetterOrSetterOrMethod(modifiers); |
return parseGetter( |
@@ -1179,12 +1203,14 @@ |
} else if (!matchesIdentifier()) { |
// TODO(brianwilkerson) Report an error and recover. |
// reportError(ParserErrorCode.?); |
+ return null; |
} else if (matches(peek(), TokenType.PERIOD) && matchesIdentifier(peek(2)) |
&& matches(peek(3), TokenType.OPEN_PAREN)) { |
return parseConstructor( |
commentAndMetadata, |
modifiers.getExternalKeyword(), |
validateModifiersForConstructor(modifiers), |
+ modifiers.getFactoryKeyword(), |
parseSimpleIdentifier(), |
getAndAdvance(), |
parseSimpleIdentifier(), |
@@ -1198,6 +1224,7 @@ |
commentAndMetadata, |
modifiers.getExternalKeyword(), |
validateModifiersForConstructor(modifiers), |
+ modifiers.getFactoryKeyword(), |
methodName, |
null, |
null, |
@@ -1212,8 +1239,7 @@ |
null, |
methodName, |
parameters); |
- } else if (matches(peek(), TokenType.EQ) || matches(peek(), TokenType.COMMA) |
- || matches(peek(), TokenType.SEMICOLON)) { |
+ } else if (matchesAny(peek(), TokenType.EQ, TokenType.COMMA, TokenType.SEMICOLON)) { |
return parseInitializedIdentifierList( |
commentAndMetadata, |
modifiers.getStaticKeyword(), |
@@ -1257,74 +1283,6 @@ |
} |
/** |
- * Parse the modifiers preceeding a class member. This method allows the modifiers to appear in |
- * any order but does generate errors for duplicated modifiers. Checks for other problems, such as |
- * specifying both 'const' and 'final', are reported elsewhere. |
- * |
- * <pre> |
- * classMemberModifiers ::= |
- * ('abstract' | 'const' | 'external' | 'factory' | 'final' | 'static' | 'var')* |
- * </pre> |
- * |
- * @return the modifiers that were parsed |
- */ |
- private Modifiers parseClassMemberModifiers() { |
- Modifiers modifiers = new Modifiers(); |
- boolean progress = true; |
- while (progress) { |
- if (matches(Keyword.ABSTRACT)) { |
- reportError(ParserErrorCode.ABSTRACT_CLASS_MEMBER); |
- advance(); |
- } else if (matches(Keyword.CONST)) { |
- if (modifiers.getConstKeyword() != null) { |
- reportError(ParserErrorCode.DUPLICATED_MODIFIER, currentToken.getLexeme()); |
- advance(); |
- } else { |
- modifiers.setConstKeyword(getAndAdvance()); |
- } |
- } else if (matches(Keyword.EXTERNAL)) { |
- if (modifiers.getExternalKeyword() != null) { |
- reportError(ParserErrorCode.DUPLICATED_MODIFIER, currentToken.getLexeme()); |
- advance(); |
- } else { |
- modifiers.setExternalKeyword(getAndAdvance()); |
- } |
- } else if (matches(Keyword.FACTORY)) { |
- if (modifiers.getFactoryKeyword() != null) { |
- reportError(ParserErrorCode.DUPLICATED_MODIFIER, currentToken.getLexeme()); |
- advance(); |
- } else { |
- modifiers.setFactoryKeyword(getAndAdvance()); |
- } |
- } else if (matches(Keyword.FINAL)) { |
- if (modifiers.getFinalKeyword() != null) { |
- reportError(ParserErrorCode.DUPLICATED_MODIFIER, currentToken.getLexeme()); |
- advance(); |
- } else { |
- modifiers.setFinalKeyword(getAndAdvance()); |
- } |
- } else if (matches(Keyword.STATIC)) { |
- if (modifiers.getStaticKeyword() != null) { |
- reportError(ParserErrorCode.DUPLICATED_MODIFIER, currentToken.getLexeme()); |
- advance(); |
- } else { |
- modifiers.setStaticKeyword(getAndAdvance()); |
- } |
- } else if (matches(Keyword.VAR)) { |
- if (modifiers.getVarKeyword() != null) { |
- reportError(ParserErrorCode.DUPLICATED_MODIFIER, currentToken.getLexeme()); |
- advance(); |
- } else { |
- modifiers.setVarKeyword(getAndAdvance()); |
- } |
- } else { |
- progress = false; |
- } |
- } |
- return modifiers; |
- } |
- |
- /** |
* Parse a list of combinators in a directive. |
* |
* <pre> |
@@ -1389,6 +1347,10 @@ |
* @return the comment reference that was parsed |
*/ |
private CommentReference parseCommentReference(String referenceSource, int sourceOffset) { |
+ // TODO(brianwilkerson) The errors are not getting the right offset/length and are being duplicated. |
+ if (referenceSource.length() == 0) { |
+ return null; |
+ } |
try { |
final boolean[] errorFound = {false}; |
AnalysisErrorListener listener = new AnalysisErrorListener() { |
@@ -1421,21 +1383,27 @@ |
nextToken = firstToken.getNext(); |
} |
if (nextToken.getType() != TokenType.EOF) { |
- reportError(ParserErrorCode.INVALID_COMMENT_REFERENCE); |
+ // TODO(brianwilkerson) Determine whether this should really be reported in these cases. |
+ // reportError(ParserErrorCode.INVALID_COMMENT_REFERENCE); |
} |
return new CommentReference(newKeyword, identifier); |
- } else if (matches(firstToken, Keyword.THIS) || matches(firstToken, Keyword.NULL)) { |
+ } else if (matches(firstToken, Keyword.THIS) || matches(firstToken, Keyword.NULL) |
+ || matches(firstToken, Keyword.TRUE) || matches(firstToken, Keyword.FALSE)) { |
// TODO(brianwilkerson) If we want to support this we will need to extend the definition |
// of CommentReference to take an expression rather than an identifier. For now we just |
// ignore it to reduce the number of errors produced, but that's probably not a valid |
// long term approach. |
return null; |
+ } else if (matches(firstToken, TokenType.STRING)) { |
+ // This is a valid link to a URI and should be ignored |
} else { |
- reportError(ParserErrorCode.INVALID_COMMENT_REFERENCE); |
+ // TODO(brianwilkerson) Determine whether this should really be reported in these cases. |
+ // reportError(ParserErrorCode.INVALID_COMMENT_REFERENCE); |
} |
} |
} catch (Exception exception) { |
- reportError(ParserErrorCode.INVALID_COMMENT_REFERENCE); |
+ // TODO(brianwilkerson) Determine whether this should really be reported in these cases. |
+ // reportError(ParserErrorCode.INVALID_COMMENT_REFERENCE); |
} |
return null; |
} |
@@ -1605,43 +1573,104 @@ |
* @return the compilation unit member that was parsed |
*/ |
private CompilationUnitMember parseCompilationUnitMember(CommentAndMetadata commentAndMetadata) { |
- if (matches(Keyword.STATIC)) { |
- reportError(ParserErrorCode.STATIC_TOP_LEVEL_DECLARATION); |
- advance(); |
- } |
- if (matches(Keyword.ABSTRACT) || matches(Keyword.CLASS)) { |
- return parseClassDeclaration(commentAndMetadata); |
+ Modifiers modifiers = parseModifiers(); |
+ if (matches(Keyword.CLASS)) { |
+ return parseClassDeclaration(commentAndMetadata, validateModifiersForClass(modifiers)); |
} else if (matches(Keyword.TYPEDEF)) { |
+ validateModifiersForTypedef(modifiers); |
return parseTypeAlias(commentAndMetadata); |
} |
- if (matches(Keyword.EXTERNAL)) { |
- Token externalKeyword = expect(Keyword.EXTERNAL); |
+ if (matches(Keyword.VOID)) { |
+ TypeName returnType = parseReturnType(); |
+ if ((matches(Keyword.GET) || matches(Keyword.SET)) && matchesIdentifier(peek())) { |
+ validateModifiersForTopLevelFunction(modifiers); |
+ return parseFunctionDeclaration( |
+ commentAndMetadata, |
+ modifiers.getExternalKeyword(), |
+ null, |
+ false); |
+ } else if (matches(Keyword.OPERATOR) && peek().isOperator()) { |
+ // TODO(brianwilkerson) Report this error and recover. |
+ return null; |
+ } else if (matchesIdentifier() |
+ && matchesAny( |
+ peek(), |
+ TokenType.OPEN_PAREN, |
+ TokenType.OPEN_CURLY_BRACKET, |
+ TokenType.FUNCTION)) { |
+ validateModifiersForTopLevelFunction(modifiers); |
+ return parseFunctionDeclaration( |
+ commentAndMetadata, |
+ modifiers.getExternalKeyword(), |
+ null, |
+ false); |
+ } else { |
+ // We have found an error of some kind. Try to recover. |
+ if (matchesIdentifier()) { |
+ if (matchesAny(peek(), TokenType.EQ, TokenType.COMMA, TokenType.SEMICOLON)) { |
+ // We appear to have a variable declaration with a type of "void". |
+ reportError(ParserErrorCode.VOID_VARIABLE, returnType); |
+ return new TopLevelVariableDeclaration( |
+ commentAndMetadata.getComment(), |
+ commentAndMetadata.getMetadata(), |
+ parseVariableDeclarationList(validateModifiersForTopLevelVariable(modifiers), null), |
+ expect(TokenType.SEMICOLON)); |
+ } |
+ } |
+ // TODO(brianwilkerson) Report this error. |
+ return null; |
+ } |
+ } else if ((matches(Keyword.GET) || matches(Keyword.SET)) && matchesIdentifier(peek())) { |
+ validateModifiersForTopLevelFunction(modifiers); |
return parseFunctionDeclaration( |
commentAndMetadata, |
- externalKeyword, |
- parseOptionalReturnType(), |
+ modifiers.getExternalKeyword(), |
+ null, |
false); |
- } else if (matches(Keyword.CONST) || matches(Keyword.FINAL) || matches(Keyword.VAR)) { |
+ } else if (matches(Keyword.OPERATOR) && peek().isOperator()) { |
+ // TODO(brianwilkerson) Report this error and recover. |
+ return null; |
+ } else if (!matchesIdentifier()) { |
+ // TODO(brianwilkerson) Report this error and recover. |
+ return null; |
+ } else if (matches(peek(), TokenType.OPEN_PAREN)) { |
+ validateModifiersForTopLevelFunction(modifiers); |
+ return parseFunctionDeclaration( |
+ commentAndMetadata, |
+ modifiers.getExternalKeyword(), |
+ null, |
+ false); |
+ } else if (matchesAny(peek(), TokenType.EQ, TokenType.COMMA, TokenType.SEMICOLON)) { |
return new TopLevelVariableDeclaration( |
commentAndMetadata.getComment(), |
commentAndMetadata.getMetadata(), |
- parseVariableDeclarationList(), |
+ parseVariableDeclarationList(validateModifiersForTopLevelVariable(modifiers), null), |
expect(TokenType.SEMICOLON)); |
- } else if (matches(Keyword.GET) || matches(Keyword.SET)) { |
- return parseFunctionDeclaration(commentAndMetadata, null, null, false); |
- } else if (matchesIdentifier() && matches(peek(), TokenType.OPEN_PAREN)) { |
- return parseFunctionDeclaration(commentAndMetadata, null, null, false); |
} |
TypeName returnType = parseReturnType(); |
if (matches(Keyword.GET) || matches(Keyword.SET)) { |
- return parseFunctionDeclaration(commentAndMetadata, null, returnType, false); |
- } else if (matchesIdentifier() && matches(peek(), TokenType.OPEN_PAREN)) { |
- return parseFunctionDeclaration(commentAndMetadata, null, returnType, false); |
+ validateModifiersForTopLevelFunction(modifiers); |
+ return parseFunctionDeclaration( |
+ commentAndMetadata, |
+ modifiers.getExternalKeyword(), |
+ returnType, |
+ false); |
+ } else if (!matchesIdentifier()) { |
+ // TODO(brianwilkerson) Report this error and recover. |
+ return null; |
} |
+ if (matchesAny(peek(), TokenType.OPEN_PAREN, TokenType.FUNCTION, TokenType.OPEN_CURLY_BRACKET)) { |
+ validateModifiersForTopLevelFunction(modifiers); |
+ return parseFunctionDeclaration( |
+ commentAndMetadata, |
+ modifiers.getExternalKeyword(), |
+ returnType, |
+ false); |
+ } |
return new TopLevelVariableDeclaration( |
commentAndMetadata.getComment(), |
commentAndMetadata.getMetadata(), |
- parseVariableDeclarationList(returnType), |
+ parseVariableDeclarationList(validateModifiersForTopLevelVariable(modifiers), returnType), |
expect(TokenType.SEMICOLON)); |
} |
@@ -1692,13 +1721,13 @@ |
} |
private ConstructorDeclaration parseConstructor(CommentAndMetadata commentAndMetadata, |
- Token externalKeyword, Token keyword, SimpleIdentifier returnType, Token period, |
- SimpleIdentifier name, FormalParameterList parameters) { |
+ Token externalKeyword, Token constKeyword, Token factoryKeyword, SimpleIdentifier returnType, |
+ Token period, SimpleIdentifier name, FormalParameterList parameters) { |
boolean bodyAllowed = externalKeyword == null; |
- Token colon = null; |
+ Token separator = null; |
List<ConstructorInitializer> initializers = null; |
if (matches(TokenType.COLON)) { |
- colon = getAndAdvance(); |
+ separator = getAndAdvance(); |
initializers = new ArrayList<ConstructorInitializer>(); |
do { |
if (matches(Keyword.THIS)) { |
@@ -1718,21 +1747,31 @@ |
} |
} while (optional(TokenType.COMMA)); |
} |
- FunctionBody body = parseFunctionBody(true, false); |
- if (!bodyAllowed && !(body instanceof EmptyFunctionBody)) { |
- reportError(ParserErrorCode.EXTERNAL_CONSTRUCTOR_WITH_BODY); |
+ ConstructorName redirectedConstructor = null; |
+ FunctionBody body; |
+ if (matches(TokenType.EQ)) { |
+ separator = getAndAdvance(); |
+ redirectedConstructor = parseConstructorName(); |
+ body = new EmptyFunctionBody(expect(TokenType.SEMICOLON)); |
+ } else { |
+ body = parseFunctionBody(true, false); |
+ if (!bodyAllowed && !(body instanceof EmptyFunctionBody)) { |
+ reportError(ParserErrorCode.EXTERNAL_CONSTRUCTOR_WITH_BODY); |
+ } |
} |
return new ConstructorDeclaration( |
commentAndMetadata.getComment(), |
commentAndMetadata.getMetadata(), |
externalKeyword, |
- keyword, |
+ constKeyword, |
+ factoryKeyword, |
returnType, |
period, |
name, |
parameters, |
- colon, |
+ separator, |
initializers, |
+ redirectedConstructor, |
body); |
} |
@@ -1741,7 +1780,7 @@ |
* |
* <pre> |
* fieldInitializer: |
- * ('this' '.')? identifier '=' conditionalExpression |
+ * ('this' '.')? identifier '=' conditionalExpression cascadeSection* |
* </pre> |
* |
* @return the field initializer that was parsed |
@@ -1756,10 +1795,43 @@ |
SimpleIdentifier fieldName = parseSimpleIdentifier(); |
Token equals = expect(TokenType.EQ); |
Expression expression = parseConditionalExpression(); |
+ TokenType tokenType = currentToken.getType(); |
+ if (tokenType == TokenType.PERIOD_PERIOD) { |
+ List<Expression> cascadeSections = new ArrayList<Expression>(); |
+ while (tokenType == TokenType.PERIOD_PERIOD) { |
+ Expression section = parseCascadeSection(); |
+ if (section != null) { |
+ cascadeSections.add(section); |
+ } |
+ tokenType = currentToken.getType(); |
+ } |
+ expression = new CascadeExpression(expression, cascadeSections); |
+ } |
return new ConstructorFieldInitializer(keyword, period, fieldName, equals, expression); |
} |
/** |
+ * Parse the name of a constructor. |
+ * |
+ * <pre> |
+ * constructorName: |
+ * type ('.' identifier)? |
+ * </pre> |
+ * |
+ * @return the constructor name that was parsed |
+ */ |
+ private ConstructorName parseConstructorName() { |
+ TypeName type = parseTypeName(); |
+ Token period = null; |
+ SimpleIdentifier name = null; |
+ if (matches(TokenType.PERIOD)) { |
+ period = getAndAdvance(); |
+ name = parseSimpleIdentifier(); |
+ } |
+ return new ConstructorName(type, period, name); |
+ } |
+ |
+ /** |
* Parse a continue statement. |
* |
* <pre> |
@@ -2470,8 +2542,15 @@ |
SimpleIdentifier name = parseSimpleIdentifier(); |
FormalParameterList parameters = null; |
if (!isGetter) { |
- parameters = parseFormalParameterList(); |
- validateFormalParameterList(parameters); |
+ if (matches(TokenType.OPEN_PAREN)) { |
+ parameters = parseFormalParameterList(); |
+ validateFormalParameterList(parameters); |
+ } else { |
+ reportError(ParserErrorCode.MISSING_FUNCTION_PARAMETERS); |
+ } |
+ } else if (matches(TokenType.OPEN_PAREN)) { |
+ reportError(ParserErrorCode.GETTER_WITH_PARAMETERS); |
+ parseFormalParameterList(); |
} |
FunctionBody body = null; |
if (externalKeyword == null) { |
@@ -2503,10 +2582,28 @@ |
* @return the function declaration statement that was parsed |
*/ |
private Statement parseFunctionDeclarationStatement() { |
+ return parseFunctionDeclarationStatement(parseCommentAndMetadata(), parseOptionalReturnType()); |
+ } |
+ |
+ /** |
+ * Parse a function declaration statement. |
+ * |
+ * <pre> |
+ * functionDeclarationStatement ::= |
+ * functionSignature functionBody |
+ * </pre> |
+ * |
+ * @param commentAndMetadata the documentation comment and metadata to be associated with the |
+ * declaration |
+ * @param returnType the return type, or {@code null} if there is no return type |
+ * @return the function declaration statement that was parsed |
+ */ |
+ private Statement parseFunctionDeclarationStatement(CommentAndMetadata commentAndMetadata, |
+ TypeName returnType) { |
return new FunctionDeclarationStatement(parseFunctionDeclaration( |
- parseCommentAndMetadata(), |
+ commentAndMetadata, |
null, |
- parseOptionalReturnType(), |
+ returnType, |
true)); |
} |
@@ -2723,15 +2820,9 @@ |
* @return the instance creation expression that was parsed |
*/ |
private InstanceCreationExpression parseInstanceCreationExpression(Token keyword) { |
- TypeName type = parseTypeName(); |
- Token period = null; |
- SimpleIdentifier identifier = null; |
- if (matches(TokenType.PERIOD)) { |
- period = getAndAdvance(); |
- identifier = parseSimpleIdentifier(); |
- } |
+ ConstructorName constructorName = parseConstructorName(); |
ArgumentList argumentList = parseArgumentList(); |
- return new InstanceCreationExpression(keyword, type, period, identifier, argumentList); |
+ return new InstanceCreationExpression(keyword, constructorName, argumentList); |
} |
/** |
@@ -2802,8 +2893,11 @@ |
*/ |
private ListLiteral parseListLiteral(Token modifier, TypeArgumentList typeArguments) { |
if (matches(TokenType.INDEX)) { |
- Token leftBracket = new Token(TokenType.OPEN_SQUARE_BRACKET, currentToken.getOffset()); |
+ BeginToken leftBracket = new BeginToken( |
+ TokenType.OPEN_SQUARE_BRACKET, |
+ currentToken.getOffset()); |
Token rightBracket = new Token(TokenType.CLOSE_SQUARE_BRACKET, currentToken.getOffset() + 1); |
+ leftBracket.setEndToken(rightBracket); |
rightBracket.setNext(currentToken.getNext()); |
leftBracket.setNext(rightBracket); |
currentToken.getPrevious().setNext(leftBracket); |
@@ -3019,6 +3113,79 @@ |
} |
/** |
+ * Parse the modifiers preceding a declaration. This method allows the modifiers to appear in any |
+ * order but does generate errors for duplicated modifiers. Checks for other problems, such as |
+ * having the modifiers appear in the wrong order or specifying both 'const' and 'final', are |
+ * reported in one of the methods whose name is prefixed with {@code validateModifiersFor}. |
+ * |
+ * <pre> |
+ * modifiers ::= |
+ * ('abstract' | 'const' | 'external' | 'factory' | 'final' | 'static' | 'var')* |
+ * </pre> |
+ * |
+ * @return the modifiers that were parsed |
+ */ |
+ private Modifiers parseModifiers() { |
+ Modifiers modifiers = new Modifiers(); |
+ boolean progress = true; |
+ while (progress) { |
+ if (matches(Keyword.ABSTRACT)) { |
+ if (modifiers.getAbstractKeyword() != null) { |
+ reportError(ParserErrorCode.DUPLICATED_MODIFIER, currentToken.getLexeme()); |
+ advance(); |
+ } else { |
+ modifiers.setAbstractKeyword(getAndAdvance()); |
+ } |
+ } else if (matches(Keyword.CONST)) { |
+ if (modifiers.getConstKeyword() != null) { |
+ reportError(ParserErrorCode.DUPLICATED_MODIFIER, currentToken.getLexeme()); |
+ advance(); |
+ } else { |
+ modifiers.setConstKeyword(getAndAdvance()); |
+ } |
+ } else if (matches(Keyword.EXTERNAL)) { |
+ if (modifiers.getExternalKeyword() != null) { |
+ reportError(ParserErrorCode.DUPLICATED_MODIFIER, currentToken.getLexeme()); |
+ advance(); |
+ } else { |
+ modifiers.setExternalKeyword(getAndAdvance()); |
+ } |
+ } else if (matches(Keyword.FACTORY)) { |
+ if (modifiers.getFactoryKeyword() != null) { |
+ reportError(ParserErrorCode.DUPLICATED_MODIFIER, currentToken.getLexeme()); |
+ advance(); |
+ } else { |
+ modifiers.setFactoryKeyword(getAndAdvance()); |
+ } |
+ } else if (matches(Keyword.FINAL)) { |
+ if (modifiers.getFinalKeyword() != null) { |
+ reportError(ParserErrorCode.DUPLICATED_MODIFIER, currentToken.getLexeme()); |
+ advance(); |
+ } else { |
+ modifiers.setFinalKeyword(getAndAdvance()); |
+ } |
+ } else if (matches(Keyword.STATIC)) { |
+ if (modifiers.getStaticKeyword() != null) { |
+ reportError(ParserErrorCode.DUPLICATED_MODIFIER, currentToken.getLexeme()); |
+ advance(); |
+ } else { |
+ modifiers.setStaticKeyword(getAndAdvance()); |
+ } |
+ } else if (matches(Keyword.VAR)) { |
+ if (modifiers.getVarKeyword() != null) { |
+ reportError(ParserErrorCode.DUPLICATED_MODIFIER, currentToken.getLexeme()); |
+ advance(); |
+ } else { |
+ modifiers.setVarKeyword(getAndAdvance()); |
+ } |
+ } else { |
+ progress = false; |
+ } |
+ } |
+ return modifiers; |
+ } |
+ |
+ /** |
* Parse a multiplicative expression. |
* |
* <pre> |
@@ -3081,6 +3248,8 @@ |
* @return the non-labeled statement that was parsed |
*/ |
private Statement parseNonLabeledStatement() { |
+ // TODO(brianwilkerson) Pass the comment and metadata on where appropriate. |
+ CommentAndMetadata commentAndMetadata = parseCommentAndMetadata(); |
if (matches(TokenType.OPEN_CURLY_BRACKET)) { |
if (matches(peek(), TokenType.STRING)) { |
Token afterString = skipStringLiteral(currentToken.getNext()); |
@@ -3117,10 +3286,33 @@ |
} else if (keyword == Keyword.VAR || keyword == Keyword.FINAL) { |
return parseVariableDeclarationStatement(); |
} else if (keyword == Keyword.VOID) { |
- return parseFunctionDeclarationStatement(); |
+ TypeName returnType = parseReturnType(); |
+ if (matchesIdentifier() |
+ && matchesAny( |
+ peek(), |
+ TokenType.OPEN_PAREN, |
+ TokenType.OPEN_CURLY_BRACKET, |
+ TokenType.FUNCTION)) { |
+ return parseFunctionDeclarationStatement(commentAndMetadata, returnType); |
+ } else { |
+ // We have found an error of some kind. Try to recover. |
+ if (matchesIdentifier()) { |
+ if (matchesAny(peek(), TokenType.EQ, TokenType.COMMA, TokenType.SEMICOLON)) { |
+ // We appear to have a variable declaration with a type of "void". |
+ reportError(ParserErrorCode.VOID_VARIABLE, returnType); |
+ return parseVariableDeclarationStatement(); |
+ } |
+ } |
+ // TODO(brianwilkerson) Report this error. |
+ return null; |
+ } |
} else if (keyword == Keyword.CONST) { |
- if (matches(peek(), TokenType.LT) || matches(peek(), TokenType.OPEN_CURLY_BRACKET) |
- || matches(peek(), TokenType.OPEN_SQUARE_BRACKET) || matches(peek(), TokenType.INDEX)) { |
+ if (matchesAny( |
+ peek(), |
+ TokenType.LT, |
+ TokenType.OPEN_CURLY_BRACKET, |
+ TokenType.OPEN_SQUARE_BRACKET, |
+ TokenType.INDEX)) { |
return new ExpressionStatement(parseExpression(), expect(TokenType.SEMICOLON)); |
} else if (matches(peek(), TokenType.IDENTIFIER)) { |
Token afterType = skipTypeName(peek()); |
@@ -3273,12 +3465,16 @@ |
* @return the return type that was parsed |
*/ |
private TypeName parseOptionalReturnType() { |
- if (matches(Keyword.VOID) |
- || (matchesIdentifier() && !matches(Keyword.GET) && !matches(Keyword.SET) |
- && !matches(Keyword.OPERATOR) && (matchesIdentifier(peek()) || matches( |
- peek(), |
- TokenType.LT)))) { |
+ if (matches(Keyword.VOID)) { |
return parseReturnType(); |
+ } else if (matchesIdentifier() && !matches(Keyword.GET) && !matches(Keyword.SET) |
+ && !matches(Keyword.OPERATOR) |
+ && (matchesIdentifier(peek()) || matches(peek(), TokenType.LT))) { |
+ return parseReturnType(); |
+ } else if (matchesIdentifier() && matches(peek(), TokenType.PERIOD) |
+ && matchesIdentifier(peek(2)) |
+ && (matchesIdentifier(peek(3)) || matches(peek(3), TokenType.LT))) { |
+ return parseReturnType(); |
} |
return null; |
} |
@@ -4039,6 +4235,28 @@ |
if (matches(TokenType.LT)) { |
typeParameters = parseTypeParameterList(); |
} |
+ if (matches(TokenType.SEMICOLON)) { |
+ reportError(ParserErrorCode.MISSING_TYPEDEF_PARAMETERS); |
+ FormalParameterList parameters = new FormalParameterList( |
+ createSyntheticToken(TokenType.OPEN_PAREN), |
+ null, |
+ null, |
+ null, |
+ createSyntheticToken(TokenType.CLOSE_PAREN)); |
+ Token semicolon = expect(TokenType.SEMICOLON); |
+ return new TypeAlias( |
+ commentAndMetadata.getComment(), |
+ commentAndMetadata.getMetadata(), |
+ keyword, |
+ returnType, |
+ name, |
+ typeParameters, |
+ parameters, |
+ semicolon); |
+ } else if (!matches(TokenType.OPEN_PAREN)) { |
+ // TODO(brianwilkerson) Report this error and recover. |
+ return null; |
+ } |
FormalParameterList parameters = parseFormalParameterList(); |
validateFormalParameterList(parameters); |
Token semicolon = expect(TokenType.SEMICOLON); |
@@ -4278,21 +4496,6 @@ |
} |
/** |
- * Parse a variable declaration list. |
- * |
- * <pre> |
- * variableDeclarationList ::= |
- * finalConstVarOrType variableDeclaration (',' variableDeclaration)* |
- * </pre> |
- * |
- * @param type the type of the variables |
- * @return the variable declaration list that was parsed |
- */ |
- private VariableDeclarationList parseVariableDeclarationList(TypeName type) { |
- return parseVariableDeclarationList(null, type); |
- } |
- |
- /** |
* Parse a variable declaration statement. |
* |
* <pre> |
@@ -4490,12 +4693,11 @@ |
// Look to see whether the token after the open parenthesis is something that should only occur |
// at the beginning of a parameter list. |
// |
- if (matches(next, TokenType.AT) |
- || matches(next, TokenType.OPEN_SQUARE_BRACKET) |
- || matches(next, TokenType.OPEN_CURLY_BRACKET) |
+ if (matchesAny(next, TokenType.AT, TokenType.OPEN_SQUARE_BRACKET, TokenType.OPEN_CURLY_BRACKET) |
|| matches(next, Keyword.VOID) |
- || (matchesIdentifier(next) && (matches(next.getNext(), TokenType.COMMA) || matches( |
+ || (matchesIdentifier(next) && (matchesAny( |
next.getNext(), |
+ TokenType.COMMA, |
TokenType.CLOSE_PAREN)))) { |
return skipPastMatchingToken(startToken); |
} |
@@ -4505,9 +4707,7 @@ |
if (matchesIdentifier(next) && matches(next.getNext(), TokenType.OPEN_PAREN)) { |
Token afterParameters = skipFormalParameterList(next.getNext()); |
if (afterParameters != null |
- && (matches(afterParameters.getNext(), TokenType.COMMA) || matches( |
- afterParameters.getNext(), |
- TokenType.CLOSE_PAREN))) { |
+ && (matchesAny(afterParameters, TokenType.COMMA, TokenType.CLOSE_PAREN))) { |
return skipPastMatchingToken(startToken); |
} |
} |
@@ -4940,13 +5140,39 @@ |
} |
/** |
+ * Validate that the given set of modifiers is appropriate for a class and return the 'abstract' |
+ * keyword if there is one. |
+ * |
+ * @param modifiers the modifiers being validated |
+ */ |
+ private Token validateModifiersForClass(Modifiers modifiers) { |
+ validateModifiersForTopLevelDeclaration(modifiers); |
+ if (modifiers.getConstKeyword() != null) { |
+ reportError(ParserErrorCode.CONST_CLASS, modifiers.getConstKeyword()); |
+ } |
+ if (modifiers.getExternalKeyword() != null) { |
+ reportError(ParserErrorCode.EXTERNAL_CLASS, modifiers.getExternalKeyword()); |
+ } |
+ if (modifiers.getFinalKeyword() != null) { |
+ reportError(ParserErrorCode.FINAL_CLASS, modifiers.getFinalKeyword()); |
+ } |
+ if (modifiers.getVarKeyword() != null) { |
+ reportError(ParserErrorCode.VAR_CLASS, modifiers.getVarKeyword()); |
+ } |
+ return modifiers.getAbstractKeyword(); |
+ } |
+ |
+ /** |
* Validate that the given set of modifiers is appropriate for a constructor and return the |
- * 'const' or 'final' keyword if there is one. |
+ * 'const' keyword if there is one. |
* |
* @param modifiers the modifiers being validated |
* @return the 'const' or 'final' keyword associated with the constructor |
*/ |
private Token validateModifiersForConstructor(Modifiers modifiers) { |
+ if (modifiers.getAbstractKeyword() != null) { |
+ reportError(ParserErrorCode.ABSTRACT_CLASS_MEMBER); |
+ } |
if (modifiers.getFinalKeyword() != null) { |
reportError(ParserErrorCode.FINAL_CONSTRUCTOR, modifiers.getFinalKeyword()); |
} |
@@ -4959,9 +5185,6 @@ |
Token externalKeyword = modifiers.getExternalKeyword(); |
Token constKeyword = modifiers.getConstKeyword(); |
Token factoryKeyword = modifiers.getFactoryKeyword(); |
- if (constKeyword != null && factoryKeyword != null) { |
- reportError(ParserErrorCode.CONST_AND_FACTORY, factoryKeyword); |
- } |
if (externalKeyword != null && constKeyword != null |
&& constKeyword.getOffset() < externalKeyword.getOffset()) { |
reportError(ParserErrorCode.EXTERNAL_AFTER_CONST, externalKeyword); |
@@ -4970,7 +5193,7 @@ |
&& factoryKeyword.getOffset() < externalKeyword.getOffset()) { |
reportError(ParserErrorCode.EXTERNAL_AFTER_FACTORY, externalKeyword); |
} |
- return lexicallyFirst(constKeyword, factoryKeyword); |
+ return constKeyword; |
} |
/** |
@@ -4981,6 +5204,9 @@ |
* @return the 'final', 'const' or 'var' keyword associated with the field |
*/ |
private Token validateModifiersForField(Modifiers modifiers) { |
+ if (modifiers.getAbstractKeyword() != null) { |
+ reportError(ParserErrorCode.ABSTRACT_CLASS_MEMBER); |
+ } |
if (modifiers.getExternalKeyword() != null) { |
reportError(ParserErrorCode.EXTERNAL_FIELD, modifiers.getExternalKeyword()); |
} |
@@ -5021,6 +5247,9 @@ |
* @param modifiers the modifiers being validated |
*/ |
private void validateModifiersForGetterOrSetterOrMethod(Modifiers modifiers) { |
+ if (modifiers.getAbstractKeyword() != null) { |
+ reportError(ParserErrorCode.ABSTRACT_CLASS_MEMBER); |
+ } |
if (modifiers.getConstKeyword() != null) { |
reportError(ParserErrorCode.CONST_METHOD, modifiers.getConstKeyword()); |
} |
@@ -5047,6 +5276,9 @@ |
* @param modifiers the modifiers being validated |
*/ |
private void validateModifiersForOperator(Modifiers modifiers) { |
+ if (modifiers.getAbstractKeyword() != null) { |
+ reportError(ParserErrorCode.ABSTRACT_CLASS_MEMBER); |
+ } |
if (modifiers.getConstKeyword() != null) { |
reportError(ParserErrorCode.CONST_METHOD, modifiers.getConstKeyword()); |
} |
@@ -5063,4 +5295,97 @@ |
reportError(ParserErrorCode.VAR_RETURN_TYPE, modifiers.getVarKeyword()); |
} |
} |
+ |
+ /** |
+ * Validate that the given set of modifiers is appropriate for a top-level declaration. |
+ * |
+ * @param modifiers the modifiers being validated |
+ */ |
+ private void validateModifiersForTopLevelDeclaration(Modifiers modifiers) { |
+ if (modifiers.getFactoryKeyword() != null) { |
+ reportError(ParserErrorCode.FACTORY_TOP_LEVEL_DECLARATION, modifiers.getFactoryKeyword()); |
+ } |
+ if (modifiers.getStaticKeyword() != null) { |
+ reportError(ParserErrorCode.STATIC_TOP_LEVEL_DECLARATION, modifiers.getStaticKeyword()); |
+ } |
+ } |
+ |
+ /** |
+ * Validate that the given set of modifiers is appropriate for a top-level function. |
+ * |
+ * @param modifiers the modifiers being validated |
+ */ |
+ private void validateModifiersForTopLevelFunction(Modifiers modifiers) { |
+ validateModifiersForTopLevelDeclaration(modifiers); |
+ if (modifiers.getAbstractKeyword() != null) { |
+ reportError(ParserErrorCode.ABSTRACT_TOP_LEVEL_FUNCTION); |
+ } |
+ if (modifiers.getConstKeyword() != null) { |
+ reportError(ParserErrorCode.CONST_CLASS, modifiers.getConstKeyword()); |
+ } |
+ if (modifiers.getFinalKeyword() != null) { |
+ reportError(ParserErrorCode.FINAL_CLASS, modifiers.getFinalKeyword()); |
+ } |
+ if (modifiers.getVarKeyword() != null) { |
+ reportError(ParserErrorCode.VAR_RETURN_TYPE, modifiers.getVarKeyword()); |
+ } |
+ } |
+ |
+ /** |
+ * Validate that the given set of modifiers is appropriate for a field and return the 'final', |
+ * 'const' or 'var' keyword if there is one. |
+ * |
+ * @param modifiers the modifiers being validated |
+ * @return the 'final', 'const' or 'var' keyword associated with the field |
+ */ |
+ private Token validateModifiersForTopLevelVariable(Modifiers modifiers) { |
+ validateModifiersForTopLevelDeclaration(modifiers); |
+ if (modifiers.getAbstractKeyword() != null) { |
+ reportError(ParserErrorCode.ABSTRACT_TOP_LEVEL_VARIABLE); |
+ } |
+ if (modifiers.getExternalKeyword() != null) { |
+ reportError(ParserErrorCode.EXTERNAL_FIELD, modifiers.getExternalKeyword()); |
+ } |
+ Token constKeyword = modifiers.getConstKeyword(); |
+ Token finalKeyword = modifiers.getFinalKeyword(); |
+ Token varKeyword = modifiers.getVarKeyword(); |
+ if (constKeyword != null) { |
+ if (finalKeyword != null) { |
+ reportError(ParserErrorCode.CONST_AND_FINAL, finalKeyword); |
+ } |
+ if (varKeyword != null) { |
+ reportError(ParserErrorCode.CONST_AND_VAR, varKeyword); |
+ } |
+ } else if (finalKeyword != null) { |
+ if (varKeyword != null) { |
+ reportError(ParserErrorCode.FINAL_AND_VAR, varKeyword); |
+ } |
+ } |
+ return lexicallyFirst(constKeyword, finalKeyword, varKeyword); |
+ } |
+ |
+ /** |
+ * Validate that the given set of modifiers is appropriate for a class and return the 'abstract' |
+ * keyword if there is one. |
+ * |
+ * @param modifiers the modifiers being validated |
+ */ |
+ private void validateModifiersForTypedef(Modifiers modifiers) { |
+ validateModifiersForTopLevelDeclaration(modifiers); |
+ if (modifiers.getAbstractKeyword() != null) { |
+ reportError(ParserErrorCode.ABSTRACT_TYPEDEF, modifiers.getAbstractKeyword()); |
+ } |
+ if (modifiers.getConstKeyword() != null) { |
+ reportError(ParserErrorCode.CONST_TYPEDEF, modifiers.getConstKeyword()); |
+ } |
+ if (modifiers.getExternalKeyword() != null) { |
+ reportError(ParserErrorCode.EXTERNAL_TYPEDEF, modifiers.getExternalKeyword()); |
+ } |
+ if (modifiers.getFinalKeyword() != null) { |
+ reportError(ParserErrorCode.FINAL_TYPEDEF, modifiers.getFinalKeyword()); |
+ } |
+ if (modifiers.getVarKeyword() != null) { |
+ reportError(ParserErrorCode.VAR_TYPEDEF, modifiers.getVarKeyword()); |
+ } |
+ } |
} |