| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.jetbrains.plugins.groovy.lang.parser.parsing.statements.declaration; |
| |
| import com.intellij.lang.PsiBuilder; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.tree.IElementType; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.groovy.GroovyBundle; |
| import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; |
| import org.jetbrains.plugins.groovy.lang.lexer.TokenSets; |
| import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes; |
| import org.jetbrains.plugins.groovy.lang.parser.GroovyParser; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.auxiliary.modifiers.Modifiers; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.statements.typeDefinitions.ReferenceElement; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.types.TypeParameters; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.types.TypeSpec; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.util.ParserUtils; |
| |
| /** |
| * @autor: Dmitry.Krasilschikov |
| * @date: 14.03.2007 |
| */ |
| |
| /* |
| * Declaration ::= modifiers [TypeSpec] VariableDefinitions |
| * | TypeSpec VariableDefinitions |
| */ |
| |
| public class Declaration { |
| |
| public static boolean parse(@NotNull PsiBuilder builder, |
| boolean isInClass, |
| boolean isInAnnotation, |
| @Nullable String typeDefinitionName, |
| @NotNull GroovyParser parser) { |
| PsiBuilder.Marker declMarker = builder.mark(); |
| //allows error messages |
| boolean modifiersParsed = Modifiers.parse(builder, parser); |
| |
| final boolean methodStart = GroovyTokenTypes.mLT == builder.getTokenType(); |
| final IElementType type = parseAfterModifiers(builder, isInClass, isInAnnotation, typeDefinitionName, parser, modifiersParsed); |
| if (type == GroovyElementTypes.WRONGWAY) { |
| if (modifiersParsed && methodStart) { |
| declMarker.error(GroovyBundle.message("method.definitions.expected")); |
| return false; |
| } |
| |
| declMarker.rollbackTo(); |
| if (modifiersParsed) { |
| builder.error(GroovyBundle.message("variable.definitions.expected")); |
| } |
| |
| return false; |
| } |
| |
| if (type != null) { |
| declMarker.done(type); |
| } else { |
| declMarker.drop(); |
| } |
| return true; |
| } |
| |
| @Nullable |
| public static IElementType parseAfterModifiers(@NotNull PsiBuilder builder, |
| boolean isInClass, |
| boolean isInAnnotation, |
| @Nullable String typeDefinitionName, |
| @NotNull GroovyParser parser, |
| boolean modifiersParsed) { |
| boolean expressionPossible = !isInAnnotation && !isInClass; |
| if (modifiersParsed && builder.getTokenType() == GroovyTokenTypes.mLT) { |
| return parseDeclarationWithGenerics(builder, isInClass, isInAnnotation, typeDefinitionName, parser, modifiersParsed, expressionPossible); |
| } |
| else if (modifiersParsed) { |
| return parseDeclarationWithoutGenerics(builder, isInClass, isInAnnotation, typeDefinitionName, parser, modifiersParsed, expressionPossible); |
| } |
| else if (typeDefinitionName != null && ParserUtils.lookAhead(builder, GroovyTokenTypes.mIDENT, GroovyTokenTypes.mLPAREN) && typeDefinitionName.equals(builder.getTokenText())) { |
| //parse constructor |
| return VariableDefinitions.parseDefinitions(builder, isInClass, isInAnnotation, typeDefinitionName, modifiersParsed, false, parser); |
| } |
| else { |
| return parsePossibleCallExpression(builder, isInClass, isInAnnotation, typeDefinitionName, parser, expressionPossible); |
| } |
| } |
| |
| private static IElementType parsePossibleCallExpression(@NotNull PsiBuilder builder, |
| boolean isInClass, |
| boolean isInAnnotation, |
| @Nullable String typeDefinitionName, |
| @NotNull GroovyParser parser, |
| boolean expressionPossible) { |
| if (isCall(builder)) { |
| return GroovyElementTypes.WRONGWAY; |
| } |
| |
| boolean typeParsed = false; |
| if (!ParserUtils.lookAhead(builder, GroovyTokenTypes.mIDENT, GroovyTokenTypes.mLPAREN)) { |
| typeParsed = TypeSpec.parse(builder, true, expressionPossible) != ReferenceElement.ReferenceElementResult.FAIL; |
| //type specification starts with upper case letter |
| if (!typeParsed) { |
| return GroovyElementTypes.WRONGWAY; |
| } |
| } |
| |
| IElementType varDef = VariableDefinitions.parseDefinitions(builder, isInClass, isInAnnotation, typeDefinitionName, typeParsed, false, parser); |
| if (varDef != GroovyElementTypes.WRONGWAY) { |
| return varDef; |
| } |
| if (isInClass && typeParsed) { |
| return null; |
| } |
| |
| return GroovyElementTypes.WRONGWAY; |
| } |
| |
| private static boolean isCall(@NotNull PsiBuilder builder) { |
| if (builder.eof()) return false; |
| if (TokenSets.BUILT_IN_TYPES.contains(builder.getTokenType())) return false; |
| |
| final String text = builder.getTokenText(); |
| if (StringUtil.isEmpty(text)) return false; |
| assert text != null; |
| |
| final char firstChar = text.charAt(0); |
| return (Character.isLowerCase(firstChar) || !Character.isLetter(firstChar)) && |
| (ParserUtils.lookAhead(builder, GroovyTokenTypes.mIDENT, GroovyTokenTypes.mIDENT) || ParserUtils.lookAhead(builder, |
| GroovyTokenTypes.mIDENT, |
| GroovyTokenTypes.mLPAREN)); |
| } |
| |
| private static IElementType parseDeclarationWithoutGenerics(@NotNull PsiBuilder builder, |
| boolean isInClass, |
| boolean isInAnnotation, |
| @Nullable String typeDefinitionName, |
| @NotNull GroovyParser parser, |
| boolean modifiersParsed, |
| boolean expressionPossible) { |
| PsiBuilder.Marker checkMarker = builder.mark(); //point to begin of type or variable |
| |
| ReferenceElement.ReferenceElementResult typeResult = TypeSpec.parse(builder, false, expressionPossible); |
| if (typeResult == ReferenceElement.ReferenceElementResult.FAIL) { //if type wasn't recognized trying parse VariableDeclaration |
| checkMarker.rollbackTo(); |
| |
| if (isInAnnotation) { |
| builder.error(GroovyBundle.message("type.expected")); |
| } |
| |
| //current token isn't identifier |
| return VariableDefinitions.parseDefinitions(builder, isInClass, isInAnnotation, typeDefinitionName, modifiersParsed, true, parser); |
| } |
| else { //type was recognized, identifier here |
| //starts after type |
| IElementType varDeclarationTop = VariableDefinitions.parseDefinitions(builder, isInClass, isInAnnotation, typeDefinitionName, |
| modifiersParsed, false, parser); |
| |
| if (varDeclarationTop == GroovyElementTypes.WRONGWAY) { |
| if (typeResult == ReferenceElement.ReferenceElementResult.REF_WITH_TYPE_PARAMS) { |
| checkMarker.drop(); |
| return GroovyElementTypes.VARIABLE_DEFINITION_ERROR; |
| } |
| |
| checkMarker.rollbackTo(); |
| |
| if (isInAnnotation) { |
| builder.error(GroovyBundle.message("type.expected")); |
| } |
| |
| //starts before "type" identifier, here can't be tuple, because next token is identifier (we are in "type recognized" branch) |
| return VariableDefinitions.parseDefinitions(builder, isInClass, isInAnnotation, typeDefinitionName, modifiersParsed, false, parser); |
| } |
| else { |
| checkMarker.drop(); |
| return varDeclarationTop; |
| } |
| } |
| } |
| |
| private static IElementType parseDeclarationWithGenerics(@NotNull PsiBuilder builder, |
| boolean isInClass, |
| boolean isInAnnotation, |
| @Nullable String typeDefinitionName, |
| @NotNull GroovyParser parser, |
| boolean modifiersParsed, |
| boolean expressionPossible) { |
| final PsiBuilder.Marker start = builder.mark(); |
| |
| final IElementType type = tryParseWithGenerics(builder, isInClass, isInAnnotation, typeDefinitionName, parser, modifiersParsed, expressionPossible, true); |
| |
| if (type == GroovyElementTypes.WRONGWAY || type == GroovyElementTypes.CONSTRUCTOR_DEFINITION || type == |
| GroovyElementTypes.METHOD_DEFINITION) { |
| start.drop(); |
| return type; |
| } |
| |
| start.rollbackTo(); |
| |
| //try to parse variable. So mark type parameters as unexpected |
| return tryParseWithGenerics(builder, isInClass, isInAnnotation, typeDefinitionName, parser, modifiersParsed, expressionPossible, false); |
| } |
| |
| private static IElementType tryParseWithGenerics(@NotNull PsiBuilder builder, |
| boolean isInClass, |
| boolean isInAnnotation, |
| @Nullable String typeDefinitionName, |
| @NotNull GroovyParser parser, |
| boolean modifiersParsed, |
| boolean expressionPossible, |
| boolean acceptTypeParameters) { |
| if (acceptTypeParameters) { |
| TypeParameters.parse(builder); |
| } |
| else { |
| final PsiBuilder.Marker error = builder.mark(); |
| TypeParameters.parse(builder); |
| error.error(GroovyBundle.message("type.parameters.are.unexpected")); |
| } |
| |
| PsiBuilder.Marker checkMarker = builder.mark(); //point to begin of type or variable |
| |
| switch (TypeSpec.parse(builder, false, expressionPossible)) { |
| case PATH_REF: |
| case REF_WITH_TYPE_PARAMS: |
| checkMarker.drop(); |
| break; |
| case FAIL: |
| checkMarker.rollbackTo(); |
| break; |
| case IDENTIFIER: |
| // declaration name element can be parsed as type element |
| IElementType result = VariableDefinitions.parseDefinitions(builder, isInClass, isInAnnotation, typeDefinitionName, modifiersParsed, false, parser); |
| if (result == GroovyElementTypes.WRONGWAY) { |
| checkMarker.rollbackTo(); |
| } |
| else { |
| checkMarker.drop(); |
| return result; |
| } |
| } |
| return VariableDefinitions.parseDefinitions(builder, isInClass, isInAnnotation, typeDefinitionName, modifiersParsed, false, parser); |
| } |
| } |
| |