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    }