/* * 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.expressions.primary; import com.intellij.lang.PsiBuilder; import com.intellij.openapi.diagnostic.Logger; 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.GroovyElementType; import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes; import org.jetbrains.plugins.groovy.lang.parser.GroovyParser; import org.jetbrains.plugins.groovy.lang.parser.parsing.statements.blocks.OpenOrClosableBlock; import org.jetbrains.plugins.groovy.lang.parser.parsing.statements.expressions.arithmetic.PathExpression; import org.jetbrains.plugins.groovy.lang.parser.parsing.util.ParserUtils; /** * @author ilyas */ public class CompoundStringExpression { private static final Logger LOG = Logger.getInstance(CompoundStringExpression.class); private final PsiBuilder myBuilder; private final GroovyParser myParser; private final boolean myForRefExpr; private final IElementType myBegin; private final IElementType myContent; private final IElementType myEnd; private final IElementType mySimpleLiteral; private final GroovyElementType myCompoundLiteral; private final String myMessage; private CompoundStringExpression(PsiBuilder builder, GroovyParser parser, boolean forRefExpr, IElementType begin, IElementType content, IElementType end, IElementType literal, GroovyElementType compoundLiteral, String message) { myBuilder = builder; myParser = parser; myForRefExpr = forRefExpr; myBegin = begin; myContent = content; myEnd = end; mySimpleLiteral = literal; myCompoundLiteral = compoundLiteral; myMessage = message; } private boolean parse() { PsiBuilder.Marker marker = myBuilder.mark(); final PsiBuilder.Marker marker2 = myBuilder.mark(); LOG.assertTrue(ParserUtils.getToken(myBuilder, myBegin)); if (mySimpleLiteral != null && myBuilder.getTokenType() == myEnd) { myBuilder.advanceLexer(); finishSimpleLiteral(marker, marker2); return true; } if (myBuilder.getTokenType() == myContent) { final PsiBuilder.Marker contentMarker = myBuilder.mark(); myBuilder.advanceLexer(); if (myBuilder.getTokenType() == GroovyTokenTypes.mDOLLAR || mySimpleLiteral == null) { contentMarker.done(GroovyElementTypes.GSTRING_CONTENT); } else { contentMarker.drop(); } } else { processContent(); } boolean hasInjection = myBuilder.getTokenType() == GroovyTokenTypes.mDOLLAR; while (myBuilder.getTokenType() == GroovyTokenTypes.mDOLLAR) { parseInjection(); processContent(); } if (!ParserUtils.getToken(myBuilder, myEnd)) { myBuilder.error(myMessage); } if (hasInjection || mySimpleLiteral == null) { marker2.drop(); marker.done(myCompoundLiteral); } else { finishSimpleLiteral(marker, marker2); } return hasInjection; } private void processContent() { PsiBuilder.Marker marker = myBuilder.mark(); if (myBuilder.getTokenType() == myContent) { myBuilder.advanceLexer(); } else { myBuilder.mark().done(myContent); } marker.done(GroovyElementTypes.GSTRING_CONTENT); } private void finishSimpleLiteral(PsiBuilder.Marker marker, PsiBuilder.Marker marker2) { marker2.done(mySimpleLiteral); if (myForRefExpr) { marker.drop(); } else { marker.done(GroovyElementTypes.LITERAL); } } /** * Parses heredoc's content in GString * * @return nothing */ private boolean parseInjection() { if (myBuilder.getTokenType() != GroovyTokenTypes.mDOLLAR) return false; final PsiBuilder.Marker injection = myBuilder.mark(); ParserUtils.getToken(myBuilder, GroovyTokenTypes.mDOLLAR); if (myBuilder.getTokenType() == GroovyTokenTypes.mIDENT || myBuilder.getTokenType() == GroovyTokenTypes.kTHIS) { PathExpression.parse(myBuilder, myParser); } else if (myBuilder.getTokenType() == GroovyTokenTypes.mLCURLY) { OpenOrClosableBlock.parseClosableBlock(myBuilder, myParser); } else { ParserUtils.wrapError(myBuilder, GroovyBundle.message("identifier.or.block.expected")); } injection.done(GroovyElementTypes.GSTRING_INJECTION); return true; } /** * Groovy lexer does not smart enough to understand whether a regex contents injections or not. So the parser should do this job. * We create additional marker2 for the case of absence of injections. In this case resulting tree is as follows: * * Regex * mRegexLiteral (mDollarSlashRegexLiteral) * mRegexBegin (........................) * mRegexContent (........................) * mRegexEnd (........................) * * This tree emulates tree of simple GrLiteralImpl structure so we can use regexes where simple strings are expected. * * @return true if there are any injections */ public static boolean parse(@NotNull PsiBuilder builder, @NotNull GroovyParser parser, boolean forRefExpr, @NotNull IElementType begin, @NotNull IElementType content, @NotNull IElementType end, @Nullable IElementType literal, @NotNull GroovyElementType compoundLiteral, @NotNull String message) { return new CompoundStringExpression(builder, parser, forRefExpr, begin, content, end, literal, compoundLiteral, message).parse(); } }