blob: 0e40d709d8810aeb1f4d65c6e967691d1c388fab [file] [log] [blame]
/*
* 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);
}
}