001/*
002 * Copyright 2010-2013 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
017package org.jetbrains.jet.lang.parsing;
018
019
020import com.intellij.lang.PsiBuilder;
021import com.intellij.lang.impl.PsiBuilderAdapter;
022import com.intellij.psi.TokenType;
023import com.intellij.psi.tree.IElementType;
024import com.intellij.psi.tree.TokenSet;
025import org.jetbrains.jet.lexer.JetTokens;
026
027import com.intellij.util.containers.Stack;
028
029
030import static org.jetbrains.jet.lexer.JetTokens.*;
031
032public 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    public SemanticWhitespaceAwarePsiBuilderImpl(PsiBuilder delegate) {
039        super(delegate);
040        newlinesEnabled.push(true);
041        joinComplexTokens.push(true);
042    }
043
044    @Override
045    public boolean newlineBeforeCurrentToken() {
046        if (!newlinesEnabled.peek()) return false;
047
048        if (eof()) return true;
049
050        // TODO: maybe, memoize this somehow?
051        for (int i = 1; i <= getCurrentOffset(); i++) {
052            IElementType previousToken = rawLookup(-i);
053
054            if (previousToken == JetTokens.BLOCK_COMMENT
055                    || previousToken == JetTokens.DOC_COMMENT
056                    || previousToken == JetTokens.EOL_COMMENT
057                    || previousToken == SHEBANG_COMMENT) {
058                continue;
059            }
060
061            if (previousToken != TokenType.WHITE_SPACE) {
062                break;
063            }
064
065            int previousTokenStart = rawTokenTypeStart(-i);
066            int previousTokenEnd = rawTokenTypeStart(-i + 1);
067
068            assert previousTokenStart >= 0;
069            assert previousTokenEnd < getOriginalText().length();
070
071            for (int j = previousTokenStart; j < previousTokenEnd; j++) {
072                if (getOriginalText().charAt(j) == '\n') {
073                    return true;
074                }
075            }
076        }
077
078        return false;
079    }
080
081    @Override
082    public void disableNewlines() {
083        newlinesEnabled.push(false);
084    }
085
086    @Override
087    public void enableNewlines() {
088        newlinesEnabled.push(true);
089    }
090
091    @Override
092    public void restoreNewlinesState() {
093        assert newlinesEnabled.size() > 1;
094        newlinesEnabled.pop();
095    }
096
097    private boolean joinComplexTokens() {
098        return joinComplexTokens.peek();
099    }
100
101    @Override
102    public void restoreJoiningComplexTokensState() {
103        joinComplexTokens.pop();
104    }
105
106    @Override
107    public void enableJoiningComplexTokens() {
108        joinComplexTokens.push(true);
109    }
110
111    @Override
112    public void disableJoiningComplexTokens() {
113        joinComplexTokens.push(false);
114    }
115
116    @Override
117    public IElementType getTokenType() {
118        if (!joinComplexTokens()) return super.getTokenType();
119        return getJoinedTokenType(super.getTokenType(), 1);
120    }
121
122    private IElementType getJoinedTokenType(IElementType rawTokenType, int rawLookupSteps) {
123        if (rawTokenType == QUEST) {
124            IElementType nextRawToken = rawLookup(rawLookupSteps);
125            if (nextRawToken == DOT) return SAFE_ACCESS;
126            if (nextRawToken == COLON) return ELVIS;
127        }
128        else if (rawTokenType == EXCL) {
129            IElementType nextRawToken = rawLookup(rawLookupSteps);
130            if (nextRawToken == EXCL) return EXCLEXCL;
131        }
132        return rawTokenType;
133    }
134
135    @Override
136    public void advanceLexer() {
137        if (!joinComplexTokens()) {
138            super.advanceLexer();
139            return;
140        }
141        IElementType tokenType = getTokenType();
142        if (complexTokens.contains(tokenType)) {
143            Marker mark = mark();
144            super.advanceLexer();
145            super.advanceLexer();
146            mark.collapse(tokenType);
147        }
148        else {
149            super.advanceLexer();
150        }
151    }
152
153    @Override
154    public String getTokenText() {
155        if (!joinComplexTokens()) return super.getTokenText();
156        IElementType tokenType = getTokenType();
157        if (complexTokens.contains(tokenType)) {
158                if (tokenType == ELVIS) return "?:";
159                if (tokenType == SAFE_ACCESS) return "?.";
160            }
161        return super.getTokenText();
162    }
163
164    @Override
165    public IElementType lookAhead(int steps) {
166        if (!joinComplexTokens()) return super.lookAhead(steps);
167
168        if (complexTokens.contains(getTokenType())) {
169            return super.lookAhead(steps + 1);
170        }
171        return getJoinedTokenType(super.lookAhead(steps), 2);
172    }
173}