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.JetTokens;
029
030 import static org.jetbrains.kotlin.lexer.JetTokens.*;
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 == JetTokens.BLOCK_COMMENT
081 || previousToken == JetTokens.DOC_COMMENT
082 || previousToken == JetTokens.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 }