blob: 49058830fc69eca02733ef137a084d95aa52754e [file] [log] [blame]
/*
* Copyright 2000-2013 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 com.intellij.indentation;
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 com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* User: Andrey.Vokin
* Date: 3/20/12
*/
public abstract class AbstractIndentParser implements PsiParser {
protected PsiBuilder myBuilder;
protected int myCurrentIndent;
protected HashMap<PsiBuilder.Marker, Integer> myIndents;
protected HashMap<PsiBuilder.Marker, Boolean> myNewLines;
protected boolean myNewLine = true;
@NotNull
public ASTNode parse(IElementType root, PsiBuilder builder) {
myNewLines = new HashMap<PsiBuilder.Marker, Boolean>();
myIndents = new HashMap<PsiBuilder.Marker, Integer>();
myBuilder = builder;
parseRoot(root);
return myBuilder.getTreeBuilt();
}
protected abstract void parseRoot(IElementType root);
public PsiBuilder.Marker mark(boolean couldBeRolledBack) {
final PsiBuilder.Marker marker = myBuilder.mark();
if (couldBeRolledBack) {
myIndents.put(marker, myCurrentIndent);
myNewLines.put(marker, myNewLine);
}
return marker;
}
public PsiBuilder.Marker mark() {
return mark(false);
}
public void done(@NotNull final PsiBuilder.Marker marker, @NotNull final IElementType elementType) {
myIndents.remove(marker);
myNewLines.remove(marker);
marker.done(elementType);
}
public static void collapse(@NotNull final PsiBuilder.Marker marker, @NotNull final IElementType elementType) {
marker.collapse(elementType);
}
protected static void drop(@NotNull final PsiBuilder.Marker marker) {
marker.drop();
}
protected void rollbackTo(@NotNull final PsiBuilder.Marker marker) {
if (myIndents.get(marker) == null) {
throw new RuntimeException("Parser can't rollback marker that was created by mark() method, use mark(true) instead");
}
myCurrentIndent = myIndents.get(marker);
myNewLine = myNewLines.get(marker);
myIndents.remove(marker);
myNewLines.remove(marker);
marker.rollbackTo();
}
protected boolean eof() {
return myBuilder.eof();
}
protected int getCurrentOffset() {
return myBuilder.getCurrentOffset();
}
public int getCurrentIndent() {
return myCurrentIndent;
}
protected void error(String message) {
myBuilder.error(message);
}
@Nullable
public IElementType getTokenType() {
return myBuilder.getTokenType();
}
protected static boolean tokenIn(@Nullable final IElementType elementType, IElementType... tokens) {
for (IElementType token : tokens) {
if (elementType == token) {
return true;
}
}
return false;
}
protected boolean currentTokenIn(IElementType... tokens) {
return tokenIn(getTokenType(), tokens);
}
protected boolean currentTokenIn(@NotNull final TokenSet tokenSet) {
return tokenSet.contains(getTokenType());
}
protected static boolean tokenIn(@Nullable final IElementType elementType, @NotNull final TokenSet tokenSet) {
return tokenSet.contains(elementType);
}
@NotNull
protected String getTokenText() {
String result = myBuilder.getTokenText();
if (result == null) {
result = "";
}
return result;
}
protected boolean expect(@NotNull final IElementType elementType) {
return expect(elementType, "Expected: " + elementType);
}
protected boolean expect(@NotNull final IElementType elementType, String expectedMessage) {
if (getTokenType() == elementType) {
advance();
return true;
}
error(expectedMessage);
return false;
}
@Nullable
public IElementType lookAhead(int step) {
return myBuilder.lookAhead(step);
}
@Nullable
public IElementType rawLookup(int step) {
return myBuilder.rawLookup(step);
}
public boolean isNewLine() {
return myNewLine;
}
public void advance() {
final String tokenText = myBuilder.getTokenText();
final int tokenLength = tokenText == null ? 0 : tokenText.length();
final int whiteSpaceStart = getCurrentOffset() + tokenLength;
myBuilder.advanceLexer();
final int whiteSpaceEnd = getCurrentOffset();
final String whiteSpaceText = myBuilder.getOriginalText().subSequence(whiteSpaceStart, whiteSpaceEnd).toString();
int i = whiteSpaceText.lastIndexOf('\n');
if (i >= 0) {
myCurrentIndent = whiteSpaceText.length() - i - 1;
myNewLine = true;
}
else {
myNewLine = false;
}
}
public void recalculateCurrentIndent() {
int i = 0;
int firstIndentOffset = myBuilder.getCurrentOffset();
while (myBuilder.rawLookup(i) != null && myBuilder.rawLookup(i) != getEolElementType()) {
firstIndentOffset = myBuilder.rawTokenTypeStart(i);
i--;
}
int lastIndentOffset = firstIndentOffset;
i++;
while (myBuilder.rawLookup(i) == getIndentElementType()) {
i++;
lastIndentOffset = myBuilder.rawTokenTypeStart(i);
}
myCurrentIndent = lastIndentOffset - firstIndentOffset;
}
protected void advanceUntil(TokenSet tokenSet) {
while (getTokenType() != null && !isNewLine() && !tokenSet.contains(getTokenType())) {
advance();
}
}
protected void advanceUntilEol() {
advanceUntil(TokenSet.EMPTY);
}
protected void errorUntil(TokenSet tokenSet, String message) {
PsiBuilder.Marker errorMarker = mark();
advanceUntil(tokenSet);
errorMarker.error(message);
}
protected void errorUntilEol(@NotNull String message) {
PsiBuilder.Marker errorMarker = mark();
advanceUntilEol();
errorMarker.error(message);
}
protected void errorUntilEof(@NotNull String message) {
PsiBuilder.Marker errorMarker = mark();
while (!eof()) {
advance();
}
errorMarker.error(message);
}
protected void expectEolOrEof() {
if (!isNewLine() && !eof()) {
errorUntilEol("End of line expected");
}
}
protected abstract IElementType getIndentElementType();
protected abstract IElementType getEolElementType();
}