| /* |
| * 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; |
| |
| import com.intellij.lang.ASTNode; |
| import com.intellij.lang.PsiBuilder; |
| import com.intellij.lang.PsiParser; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.tree.TokenSet; |
| 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.parser.parsing.auxiliary.Separators; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.auxiliary.modifiers.Modifiers; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.statements.*; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.statements.blocks.OpenOrClosableBlock; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.statements.constructor.ConstructorBody; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.statements.declaration.Declaration; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.statements.expressions.AssignmentExpression; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.statements.expressions.ConditionalExpression; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.statements.expressions.ExpressionStatement; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.statements.imports.ImportStatement; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.statements.typeDefinitions.TypeDefinition; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.toplevel.CompilationUnit; |
| import org.jetbrains.plugins.groovy.lang.parser.parsing.util.ParserUtils; |
| |
| /** |
| * Parser for Groovy script files |
| * |
| * @author ilyas, Dmitry.Krasilschikov |
| */ |
| public class GroovyParser implements PsiParser { |
| |
| public static final TokenSet RCURLY_ONLY = TokenSet.create(GroovyTokenTypes.mRCURLY); |
| public static final TokenSet CASE_SECTION_END = TokenSet.create(GroovyTokenTypes.kCASE, GroovyTokenTypes.kDEFAULT, |
| GroovyTokenTypes.mRCURLY); |
| |
| public boolean parseDeep() { |
| return false; |
| } |
| |
| public static void parseExpression(PsiBuilder builder) { |
| ExpressionStatement.argParse(builder, new GroovyParser()); |
| } |
| |
| @Override |
| @NotNull |
| public ASTNode parse(IElementType root, PsiBuilder builder) { |
| //builder.setDebugMode(true); |
| if (root == GroovyElementTypes.OPEN_BLOCK) { |
| OpenOrClosableBlock.parseOpenBlockDeep(builder, this); |
| } |
| else if (root == GroovyElementTypes.CLOSABLE_BLOCK) { |
| OpenOrClosableBlock.parseClosableBlockDeep(builder, this); |
| } |
| else if (root == GroovyElementTypes.CONSTRUCTOR_BODY) { |
| ConstructorBody.parseConstructorBodyDeep(builder, this); |
| } |
| else { |
| assert root == GroovyParserDefinition.GROOVY_FILE : root; |
| PsiBuilder.Marker rootMarker = builder.mark(); |
| CompilationUnit.parseFile(builder, this); |
| rootMarker.done(root); |
| } |
| return builder.getTreeBuilt(); |
| } |
| |
| public boolean parseForStatement(PsiBuilder builder) { |
| PsiBuilder.Marker marker = builder.mark(); |
| |
| ParserUtils.getToken(builder, GroovyTokenTypes.kFOR); |
| if (!ParserUtils.getToken(builder, GroovyTokenTypes.mLPAREN, GroovyBundle.message("lparen.expected"))) { |
| marker.done(GroovyElementTypes.FOR_STATEMENT); |
| return true; |
| } |
| if (!ForStatement.forClauseParse(builder, this)) { |
| builder.error(GroovyBundle.message("for.clause.expected")); |
| marker.done(GroovyElementTypes.FOR_STATEMENT); |
| return true; |
| } |
| |
| ParserUtils.getToken(builder, GroovyTokenTypes.mNLS); |
| |
| if (!ParserUtils.getToken(builder, GroovyTokenTypes.mRPAREN, GroovyBundle.message("rparen.expected"))) { |
| ParserUtils.getToken(builder, GroovyTokenTypes.mNLS); |
| marker.done(GroovyElementTypes.FOR_STATEMENT); |
| return true; |
| } |
| |
| PsiBuilder.Marker warn = builder.mark(); |
| ParserUtils.getToken(builder, GroovyTokenTypes.mNLS); |
| |
| if (parseExtendedStatement(builder)) { |
| warn.rollbackTo(); |
| marker.done(GroovyElementTypes.FOR_STATEMENT); |
| return true; |
| } |
| |
| if (parseStatement(builder, true)) { |
| warn.drop(); |
| } |
| else { |
| warn.rollbackTo(); |
| builder.error(GroovyBundle.message("statement.expected")); |
| } |
| marker.done(GroovyElementTypes.FOR_STATEMENT); |
| return true; |
| } |
| |
| public boolean parseIfStatement(PsiBuilder builder) { |
| //allow error messages |
| PsiBuilder.Marker ifStmtMarker = builder.mark(); |
| if (!ParserUtils.getToken(builder, GroovyTokenTypes.kIF)) { |
| ifStmtMarker.rollbackTo(); |
| builder.error(GroovyBundle.message("if.expected")); |
| return false; |
| } |
| |
| if (!ParserUtils.getToken(builder, GroovyTokenTypes.mLPAREN, GroovyBundle.message("lparen.expected"))) { |
| ifStmtMarker.done(GroovyElementTypes.IF_STATEMENT); |
| return true; |
| } |
| |
| if (!ConditionalExpression.parse(builder, this)) { |
| builder.error(GroovyBundle.message("expression.expected")); |
| } |
| |
| ParserUtils.getToken(builder, GroovyTokenTypes.mNLS); |
| ParserUtils.getToken(builder, GroovyTokenTypes.mRPAREN, GroovyBundle.message("rparen.expected")); |
| |
| if (!parseBranch(builder)) { |
| ifStmtMarker.done(GroovyElementTypes.IF_STATEMENT); |
| return true; |
| } |
| |
| PsiBuilder.Marker rb = builder.mark(); |
| if (GroovyTokenTypes.kELSE.equals(builder.getTokenType()) || |
| (Separators.parse(builder) && builder.getTokenType() == GroovyTokenTypes.kELSE)) { |
| rb.drop(); |
| ParserUtils.getToken(builder, GroovyTokenTypes.kELSE); |
| |
| parseBranch(builder); |
| } |
| else { |
| rb.rollbackTo(); |
| } |
| |
| ifStmtMarker.done(GroovyElementTypes.IF_STATEMENT); |
| return true; |
| } |
| |
| public void parseSwitchCaseList(PsiBuilder builder) { |
| if (parseGenericStatement(builder, CASE_SECTION_END)) { |
| parseCodeBlock(builder, CASE_SECTION_END); |
| } |
| } |
| |
| //gsp directives, scriptlets and such |
| protected boolean isExtendedSeparator(@Nullable final IElementType tokenType) { |
| return false; |
| } |
| |
| //gsp template statement, for example |
| protected boolean parseExtendedStatement(PsiBuilder builder) { |
| return false; |
| } |
| |
| public boolean parseWhileStatement(PsiBuilder builder) { |
| |
| PsiBuilder.Marker marker = builder.mark(); |
| |
| ParserUtils.getToken(builder, GroovyTokenTypes.kWHILE); |
| |
| if (!ParserUtils.getToken(builder, GroovyTokenTypes.mLPAREN, GroovyBundle.message("lparen.expected"))) { |
| marker.done(GroovyElementTypes.WHILE_STATEMENT); |
| return true; |
| } |
| |
| if (!ExpressionStatement.argParse(builder, this)) { |
| builder.error(GroovyBundle.message("expression.expected")); |
| } |
| |
| ParserUtils.getToken(builder, GroovyTokenTypes.mNLS); |
| |
| if (!ParserUtils.getToken(builder, GroovyTokenTypes.mRPAREN, GroovyBundle.message("rparen.expected"))) { |
| marker.done(GroovyElementTypes.WHILE_STATEMENT); |
| return true; |
| } |
| |
| parseBranch(builder); |
| marker.done(GroovyElementTypes.WHILE_STATEMENT); |
| return true; |
| } |
| |
| private boolean parseBranch(@NotNull PsiBuilder builder) { |
| PsiBuilder.Marker warn = builder.mark(); |
| ParserUtils.getToken(builder, GroovyTokenTypes.mNLS); |
| |
| if (!parseStatement(builder, true) && !parseExtendedStatement(builder)) { |
| warn.rollbackTo(); |
| builder.error(GroovyBundle.message("statement.expected")); |
| return false; |
| } |
| else { |
| warn.drop(); |
| return true; |
| } |
| } |
| |
| public void parseBlockBody(PsiBuilder builder) { |
| skipSeparators(builder); |
| parseBlockBodyWithoutSkippingSeparators(builder); |
| } |
| |
| public void parseBlockBodyWithoutSkippingSeparators(PsiBuilder builder) { |
| parseCodeBlock(builder, RCURLY_ONLY); |
| ParserUtils.getToken(builder, GroovyTokenTypes.mNLS); |
| } |
| |
| private void parseCodeBlock(PsiBuilder builder, TokenSet until) { |
| while (true) { |
| if (builder.eof() || until.contains(builder.getTokenType())) break; |
| if (!parseGenericStatement(builder, until)) break; |
| } |
| } |
| |
| private boolean parseGenericStatement(PsiBuilder builder, TokenSet until) { |
| boolean plainStatement = parseStatement(builder, false); |
| |
| if (plainStatement || parseExtendedStatement(builder)) { |
| if (parseSeparatorsWithoutLastNls(builder, plainStatement, until)) { |
| return false; |
| } |
| } |
| else { |
| builder.error(GroovyBundle.message("wrong.statement")); |
| assert builder.getTokenType() != GroovyTokenTypes.mLCURLY && builder.getTokenType() != GroovyTokenTypes.mRCURLY; |
| builder.advanceLexer(); |
| } |
| return true; |
| } |
| |
| private boolean parseSeparatorsWithoutLastNls(PsiBuilder builder, boolean requireSeparator, TokenSet until) { |
| boolean hasSeparator = false; |
| while (true) { |
| while (builder.getTokenType() == GroovyTokenTypes.mSEMI || isExtendedSeparator(builder.getTokenType())) { |
| hasSeparator = true; |
| builder.advanceLexer(); |
| } |
| |
| if (builder.getTokenType() == GroovyTokenTypes.mNLS) { |
| PsiBuilder.Marker beforeNls = builder.mark(); |
| hasSeparator = true; |
| builder.advanceLexer(); |
| if (builder.eof() || until.contains(builder.getTokenType())) { |
| beforeNls.rollbackTo(); |
| return true; |
| } |
| beforeNls.drop(); |
| } |
| else { |
| break; |
| } |
| } |
| if (builder.eof() || until.contains(builder.getTokenType())) { |
| return true; |
| } |
| if (requireSeparator && !hasSeparator) { |
| builder.error(GroovyBundle.message("separator.or.rcurly.expected")); |
| } |
| return false; |
| } |
| |
| private boolean skipSeparators(PsiBuilder builder) { |
| boolean hasSeparators = false; |
| while (builder.getTokenType() == GroovyTokenTypes.mSEMI || isExtendedSeparator(builder.getTokenType()) || builder.getTokenType() == |
| GroovyTokenTypes.mNLS) { |
| hasSeparators = true; |
| builder.advanceLexer(); |
| } |
| return hasSeparators; |
| } |
| |
| public boolean parseStatement(PsiBuilder builder, boolean isBlockStatementNeeded) { |
| if (isBlockStatementNeeded && GroovyTokenTypes.mLCURLY.equals(builder.getTokenType())) { |
| final PsiBuilder.Marker marker = builder.mark(); |
| OpenOrClosableBlock.parseOpenBlockDeep(builder, this); |
| marker.done(GroovyElementTypes.BLOCK_STATEMENT); |
| return true; |
| } |
| |
| if (isBlockStatementNeeded && GroovyTokenTypes.mSEMI == builder.getTokenType()) { |
| return true; |
| } |
| |
| if (GroovyTokenTypes.kIMPORT.equals(builder.getTokenType())) { |
| PsiBuilder.Marker marker = builder.mark(); |
| ImportStatement.parse(builder, this); |
| marker.error(GroovyBundle.message("import.not.allowed")); |
| return true; |
| } |
| |
| if (GroovyTokenTypes.kIF.equals(builder.getTokenType())) { |
| return parseIfStatement(builder); |
| } |
| if (GroovyTokenTypes.kSWITCH.equals(builder.getTokenType())) { |
| SwitchStatement.parseSwitch(builder, this); |
| return true; |
| } |
| if (GroovyTokenTypes.kTRY.equals(builder.getTokenType())) { |
| return TryCatchStatement.parse(builder, this); |
| } |
| if (GroovyTokenTypes.kWHILE.equals(builder.getTokenType())) { |
| return parseWhileStatement(builder); |
| } |
| if (GroovyTokenTypes.kFOR.equals(builder.getTokenType())) { |
| return parseForStatement(builder); |
| } |
| if (ParserUtils.lookAhead(builder, GroovyTokenTypes.kSYNCHRONIZED, GroovyTokenTypes.mLPAREN)) { |
| PsiBuilder.Marker synMarker = builder.mark(); |
| if (SynchronizedStatement.parse(builder, this)) { |
| synMarker.drop(); |
| return true; |
| } |
| else { |
| synMarker.rollbackTo(); |
| } |
| } |
| |
| // Possible errors |
| if (GroovyTokenTypes.kELSE.equals(builder.getTokenType())) { |
| ParserUtils.wrapError(builder, GroovyBundle.message("else.without.if")); |
| parseStatement(builder, true); |
| return true; |
| } |
| if (GroovyTokenTypes.kCATCH.equals(builder.getTokenType())) { |
| ParserUtils.wrapError(builder, GroovyBundle.message("catch.without.try")); |
| parseStatement(builder, false); |
| return true; |
| } |
| if (GroovyTokenTypes.kFINALLY.equals(builder.getTokenType())) { |
| ParserUtils.wrapError(builder, GroovyBundle.message("finally.without.try")); |
| parseStatement(builder, false); |
| return true; |
| } |
| if (GroovyTokenTypes.kCASE.equals(builder.getTokenType())) { |
| PsiBuilder.Marker marker = builder.mark(); |
| SwitchStatement.parseCaseLabel(builder, this); |
| marker.error(GroovyBundle.message("case.without.switch")); |
| parseStatement(builder, false); |
| return true; |
| } |
| if (GroovyTokenTypes.kDEFAULT.equals(builder.getTokenType())) { |
| PsiBuilder.Marker marker = builder.mark(); |
| SwitchStatement.parseCaseLabel(builder, this); |
| marker.error(GroovyBundle.message("default.without.switch")); |
| parseStatement(builder, false); |
| return true; |
| } |
| |
| if (BranchStatement.BRANCH_KEYWORDS.contains(builder.getTokenType())) { |
| return BranchStatement.parse(builder, this); |
| } |
| if (parseLabeledStatement(builder)) { |
| return true; |
| } |
| |
| if (parseDeclaration(builder, false, false, null)) return true; |
| |
| return AssignmentExpression.parse(builder, this, true); |
| } |
| |
| /** |
| * parses imports (marks them as not allowed), type definitions, methods, variables or fields (if isInClass), initializers (if isInClass), constructors |
| * with corresponding typeDefinitionName |
| * <p/> |
| * If non of preceding elements was found rolls back and return false |
| */ |
| public boolean parseDeclaration(@NotNull PsiBuilder builder, |
| boolean isInClass, |
| boolean isInAnnotation, |
| @Nullable String typeDefinitionName) { |
| PsiBuilder.Marker declMarker = builder.mark(); |
| boolean modifiersParsed = Modifiers.parse(builder, this); |
| |
| if (GroovyTokenTypes.kIMPORT == builder.getTokenType()) { |
| final PsiBuilder.Marker impMarker = declMarker.precede(); |
| ImportStatement.parseAfterModifiers(builder); |
| declMarker.done(GroovyElementTypes.IMPORT_STATEMENT); |
| impMarker.error(GroovyBundle.message("import.not.allowed")); |
| return true; |
| } |
| |
| if (isTypeDefinitionStart(builder)) { |
| final IElementType tdType = TypeDefinition.parseAfterModifiers(builder, this); |
| if (tdType != GroovyElementTypes.WRONGWAY) { |
| declMarker.done(tdType); |
| } |
| else { |
| builder.error(GroovyBundle.message("identifier.expected")); |
| declMarker.drop(); |
| } |
| return true; |
| } |
| |
| if (isInClass && parseInitializer(builder)) { |
| declMarker.done(GroovyElementTypes.CLASS_INITIALIZER); |
| return true; |
| } |
| |
| final IElementType declType = |
| Declaration.parseAfterModifiers(builder, isInClass, isInAnnotation, typeDefinitionName, this, modifiersParsed); |
| if (declType != GroovyElementTypes.WRONGWAY) { |
| if (declType != null) { |
| declMarker.done(declType); |
| } |
| else { |
| declMarker.drop(); |
| } |
| return true; |
| } |
| |
| if (modifiersParsed) { |
| declMarker.drop(); |
| builder.error(GroovyBundle.message("identifier.expected")); |
| return true; |
| } |
| |
| declMarker.rollbackTo(); |
| return false; |
| } |
| |
| private boolean parseInitializer(PsiBuilder builder) { |
| ParserUtils.getToken(builder, GroovyTokenTypes.mNLS); |
| return GroovyTokenTypes.mLCURLY == builder.getTokenType() && OpenOrClosableBlock.parseOpenBlock(builder, this); |
| } |
| |
| private static boolean isTypeDefinitionStart(PsiBuilder builder) { |
| return GroovyTokenTypes.kCLASS == builder.getTokenType() || //class |
| GroovyTokenTypes.kINTERFACE == builder.getTokenType() || //interface |
| GroovyTokenTypes.kENUM == builder.getTokenType() || //enum |
| GroovyTokenTypes.kTRAIT == builder.getTokenType() || //trait |
| ParserUtils.lookAhead(builder, GroovyTokenTypes.mAT, GroovyTokenTypes.kINTERFACE); //@interface |
| } |
| |
| public boolean parseStatementWithImports(PsiBuilder builder) { |
| if (ImportStatement.parse(builder, this)) { |
| return true; |
| } |
| else { |
| return parseStatement(builder, false); |
| } |
| } |
| |
| private boolean parseLabeledStatement(PsiBuilder builder) { |
| |
| PsiBuilder.Marker marker = builder.mark(); |
| |
| if (!ParserUtils.getToken(builder, GroovyTokenTypes.mIDENT) || !ParserUtils.getToken(builder, GroovyTokenTypes.mCOLON)) { |
| marker.rollbackTo(); |
| return false; |
| } |
| |
| final PsiBuilder.Marker nlsMarker = builder.mark(); |
| |
| ParserUtils.getToken(builder, GroovyTokenTypes.mNLS); |
| if (parseStatement(builder, true)) { |
| nlsMarker.drop(); |
| } |
| else { |
| nlsMarker.rollbackTo(); |
| builder.error(GroovyBundle.message("statement.expected")); |
| } |
| |
| marker.done(GroovyElementTypes.LABELED_STATEMENT); |
| return true; |
| } |
| } |