/* * Copyright 2010-2015 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.kotlin.parsing; import com.intellij.lang.PsiBuilder; import com.intellij.lang.impl.PsiBuilderAdapter; import com.intellij.lang.impl.PsiBuilderImpl; import com.intellij.psi.TokenType; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.util.containers.Stack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.lexer.KtTokens; import static org.jetbrains.kotlin.lexer.KtTokens.*; public class SemanticWhitespaceAwarePsiBuilderImpl extends PsiBuilderAdapter implements SemanticWhitespaceAwarePsiBuilder { private final TokenSet complexTokens = TokenSet.create(SAFE_ACCESS, ELVIS, EXCLEXCL); private final Stack<Boolean> joinComplexTokens = new Stack<>(); private final Stack<Boolean> newlinesEnabled = new Stack<>(); private final PsiBuilderImpl delegateImpl; public SemanticWhitespaceAwarePsiBuilderImpl(PsiBuilder delegate) { super(delegate); newlinesEnabled.push(true); joinComplexTokens.push(true); delegateImpl = findPsiBuilderImpl(delegate); } @Nullable private static PsiBuilderImpl findPsiBuilderImpl(PsiBuilder builder) { // This is a hackish workaround for PsiBuilder interface not exposing isWhitespaceOrComment() method // We have to unwrap all the adapters to find an Impl inside while (true) { if (builder instanceof PsiBuilderImpl) { return (PsiBuilderImpl) builder; } if (!(builder instanceof PsiBuilderAdapter)) { return null; } builder = ((PsiBuilderAdapter) builder).getDelegate(); } } @Override public boolean isWhitespaceOrComment(@NotNull IElementType elementType) { assert delegateImpl != null : "PsiBuilderImpl not found"; return delegateImpl.whitespaceOrComment(elementType); } @Override public boolean newlineBeforeCurrentToken() { if (!newlinesEnabled.peek()) return false; if (eof()) return true; // TODO: maybe, memoize this somehow? for (int i = 1; i <= getCurrentOffset(); i++) { IElementType previousToken = rawLookup(-i); if (previousToken == KtTokens.BLOCK_COMMENT || previousToken == KtTokens.DOC_COMMENT || previousToken == KtTokens.EOL_COMMENT || previousToken == SHEBANG_COMMENT) { continue; } if (previousToken != TokenType.WHITE_SPACE) { break; } int previousTokenStart = rawTokenTypeStart(-i); int previousTokenEnd = rawTokenTypeStart(-i + 1); assert previousTokenStart >= 0; assert previousTokenEnd < getOriginalText().length(); for (int j = previousTokenStart; j < previousTokenEnd; j++) { if (getOriginalText().charAt(j) == '\n') { return true; } } } return false; } @Override public void disableNewlines() { newlinesEnabled.push(false); } @Override public void enableNewlines() { newlinesEnabled.push(true); } @Override public void restoreNewlinesState() { assert newlinesEnabled.size() > 1; newlinesEnabled.pop(); } private boolean joinComplexTokens() { return joinComplexTokens.peek(); } @Override public void restoreJoiningComplexTokensState() { joinComplexTokens.pop(); } @Override public void enableJoiningComplexTokens() { joinComplexTokens.push(true); } @Override public void disableJoiningComplexTokens() { joinComplexTokens.push(false); } @Override public IElementType getTokenType() { if (!joinComplexTokens()) return super.getTokenType(); return getJoinedTokenType(super.getTokenType(), 1); } private IElementType getJoinedTokenType(IElementType rawTokenType, int rawLookupSteps) { if (rawTokenType == QUEST) { IElementType nextRawToken = rawLookup(rawLookupSteps); if (nextRawToken == DOT) return SAFE_ACCESS; if (nextRawToken == COLON) return ELVIS; } else if (rawTokenType == EXCL) { IElementType nextRawToken = rawLookup(rawLookupSteps); if (nextRawToken == EXCL) return EXCLEXCL; } return rawTokenType; } @Override public void advanceLexer() { if (!joinComplexTokens()) { super.advanceLexer(); return; } IElementType tokenType = getTokenType(); if (complexTokens.contains(tokenType)) { Marker mark = mark(); super.advanceLexer(); super.advanceLexer(); mark.collapse(tokenType); } else { super.advanceLexer(); } } @Override public String getTokenText() { if (!joinComplexTokens()) return super.getTokenText(); IElementType tokenType = getTokenType(); if (complexTokens.contains(tokenType)) { if (tokenType == ELVIS) return "?:"; if (tokenType == SAFE_ACCESS) return "?."; } return super.getTokenText(); } @Override public IElementType lookAhead(int steps) { if (!joinComplexTokens()) return super.lookAhead(steps); if (complexTokens.contains(getTokenType())) { return super.lookAhead(steps + 1); } return getJoinedTokenType(super.lookAhead(steps), 2); } }