001 /* 002 * Copyright 2010-2015 JetBrains s.r.o. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package org.jetbrains.kotlin.parsing; 018 019 import com.intellij.lang.PsiBuilder; 020 import com.intellij.lang.impl.PsiBuilderAdapter; 021 import com.intellij.lang.impl.PsiBuilderImpl; 022 import com.intellij.psi.TokenType; 023 import com.intellij.psi.tree.IElementType; 024 import com.intellij.psi.tree.TokenSet; 025 import com.intellij.util.containers.Stack; 026 import org.jetbrains.annotations.NotNull; 027 import org.jetbrains.annotations.Nullable; 028 import org.jetbrains.kotlin.lexer.KtTokens; 029 030 import static org.jetbrains.kotlin.lexer.KtTokens.*; 031 032 public class SemanticWhitespaceAwarePsiBuilderImpl extends PsiBuilderAdapter implements SemanticWhitespaceAwarePsiBuilder { 033 private final TokenSet complexTokens = TokenSet.create(SAFE_ACCESS, ELVIS, EXCLEXCL); 034 private final Stack<Boolean> joinComplexTokens = new Stack<Boolean>(); 035 036 private final Stack<Boolean> newlinesEnabled = new Stack<Boolean>(); 037 038 private final PsiBuilderImpl delegateImpl; 039 040 public SemanticWhitespaceAwarePsiBuilderImpl(PsiBuilder delegate) { 041 super(delegate); 042 newlinesEnabled.push(true); 043 joinComplexTokens.push(true); 044 045 delegateImpl = findPsiBuilderImpl(delegate); 046 } 047 048 @Nullable 049 private static PsiBuilderImpl findPsiBuilderImpl(PsiBuilder builder) { 050 // This is a hackish workaround for PsiBuilder interface not exposing isWhitespaceOrComment() method 051 // We have to unwrap all the adapters to find an Impl inside 052 while (true) { 053 if (builder instanceof PsiBuilderImpl) { 054 return (PsiBuilderImpl) builder; 055 } 056 if (!(builder instanceof PsiBuilderAdapter)) { 057 return null; 058 } 059 060 builder = ((PsiBuilderAdapter) builder).getDelegate(); 061 } 062 } 063 064 @Override 065 public boolean isWhitespaceOrComment(@NotNull IElementType elementType) { 066 assert delegateImpl != null : "PsiBuilderImpl not found"; 067 return delegateImpl.whitespaceOrComment(elementType); 068 } 069 070 @Override 071 public boolean newlineBeforeCurrentToken() { 072 if (!newlinesEnabled.peek()) return false; 073 074 if (eof()) return true; 075 076 // TODO: maybe, memoize this somehow? 077 for (int i = 1; i <= getCurrentOffset(); i++) { 078 IElementType previousToken = rawLookup(-i); 079 080 if (previousToken == KtTokens.BLOCK_COMMENT 081 || previousToken == KtTokens.DOC_COMMENT 082 || previousToken == KtTokens.EOL_COMMENT 083 || previousToken == SHEBANG_COMMENT) { 084 continue; 085 } 086 087 if (previousToken != TokenType.WHITE_SPACE) { 088 break; 089 } 090 091 int previousTokenStart = rawTokenTypeStart(-i); 092 int previousTokenEnd = rawTokenTypeStart(-i + 1); 093 094 assert previousTokenStart >= 0; 095 assert previousTokenEnd < getOriginalText().length(); 096 097 for (int j = previousTokenStart; j < previousTokenEnd; j++) { 098 if (getOriginalText().charAt(j) == '\n') { 099 return true; 100 } 101 } 102 } 103 104 return false; 105 } 106 107 @Override 108 public void disableNewlines() { 109 newlinesEnabled.push(false); 110 } 111 112 @Override 113 public void enableNewlines() { 114 newlinesEnabled.push(true); 115 } 116 117 @Override 118 public void restoreNewlinesState() { 119 assert newlinesEnabled.size() > 1; 120 newlinesEnabled.pop(); 121 } 122 123 private boolean joinComplexTokens() { 124 return joinComplexTokens.peek(); 125 } 126 127 @Override 128 public void restoreJoiningComplexTokensState() { 129 joinComplexTokens.pop(); 130 } 131 132 @Override 133 public void enableJoiningComplexTokens() { 134 joinComplexTokens.push(true); 135 } 136 137 @Override 138 public void disableJoiningComplexTokens() { 139 joinComplexTokens.push(false); 140 } 141 142 @Override 143 public IElementType getTokenType() { 144 if (!joinComplexTokens()) return super.getTokenType(); 145 return getJoinedTokenType(super.getTokenType(), 1); 146 } 147 148 private IElementType getJoinedTokenType(IElementType rawTokenType, int rawLookupSteps) { 149 if (rawTokenType == QUEST) { 150 IElementType nextRawToken = rawLookup(rawLookupSteps); 151 if (nextRawToken == DOT) return SAFE_ACCESS; 152 if (nextRawToken == COLON) return ELVIS; 153 } 154 else if (rawTokenType == EXCL) { 155 IElementType nextRawToken = rawLookup(rawLookupSteps); 156 if (nextRawToken == EXCL) return EXCLEXCL; 157 } 158 return rawTokenType; 159 } 160 161 @Override 162 public void advanceLexer() { 163 if (!joinComplexTokens()) { 164 super.advanceLexer(); 165 return; 166 } 167 IElementType tokenType = getTokenType(); 168 if (complexTokens.contains(tokenType)) { 169 Marker mark = mark(); 170 super.advanceLexer(); 171 super.advanceLexer(); 172 mark.collapse(tokenType); 173 } 174 else { 175 super.advanceLexer(); 176 } 177 } 178 179 @Override 180 public String getTokenText() { 181 if (!joinComplexTokens()) return super.getTokenText(); 182 IElementType tokenType = getTokenType(); 183 if (complexTokens.contains(tokenType)) { 184 if (tokenType == ELVIS) return "?:"; 185 if (tokenType == SAFE_ACCESS) return "?."; 186 } 187 return super.getTokenText(); 188 } 189 190 @Override 191 public IElementType lookAhead(int steps) { 192 if (!joinComplexTokens()) return super.lookAhead(steps); 193 194 if (complexTokens.contains(getTokenType())) { 195 return super.lookAhead(steps + 1); 196 } 197 return getJoinedTokenType(super.lookAhead(steps), 2); 198 } 199 }