| /* |
| * Copyright 2000-2014 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 com.intellij.embedding; |
| |
| import com.intellij.lang.ASTNode; |
| import com.intellij.lang.LighterLazyParseableNode; |
| import com.intellij.lang.ParserDefinition; |
| import com.intellij.lang.PsiBuilder; |
| import com.intellij.lang.impl.DelegateMarker; |
| import com.intellij.lang.impl.PsiBuilderAdapter; |
| import com.intellij.lang.impl.PsiBuilderImpl; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.psi.TokenType; |
| import com.intellij.psi.tree.IElementType; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * A delegate PsiBuilder that hides or substitutes some tokens (namely, the ones provided by {@link MasqueradingLexer}) |
| * from a parser, however, _still inserting_ them into a production tree in their initial appearance. |
| * @see MasqueradingLexer |
| */ |
| public class MasqueradingPsiBuilderAdapter extends PsiBuilderAdapter { |
| private final static Logger LOG = Logger.getInstance(MasqueradingPsiBuilderAdapter.class); |
| |
| private List<MyShiftedToken> myShrunkSequence; |
| |
| private CharSequence myShrunkCharSequence; |
| |
| private int myLexPosition; |
| |
| private final PsiBuilderImpl myBuilderDelegate; |
| |
| private final MasqueradingLexer myLexer; |
| |
| public MasqueradingPsiBuilderAdapter(@NotNull final Project project, |
| @NotNull final ParserDefinition parserDefinition, |
| @NotNull final MasqueradingLexer lexer, |
| @NotNull final ASTNode chameleon, |
| @NotNull final CharSequence text) { |
| this(new PsiBuilderImpl(project, parserDefinition, lexer, chameleon, text)); |
| } |
| |
| public MasqueradingPsiBuilderAdapter(@NotNull final Project project, |
| @NotNull final ParserDefinition parserDefinition, |
| @NotNull final MasqueradingLexer lexer, |
| @NotNull final LighterLazyParseableNode chameleon, |
| @NotNull final CharSequence text) { |
| this(new PsiBuilderImpl(project, parserDefinition, lexer, chameleon, text)); |
| } |
| |
| private MasqueradingPsiBuilderAdapter(PsiBuilderImpl builder) { |
| super(builder); |
| |
| LOG.assertTrue(myDelegate instanceof PsiBuilderImpl); |
| myBuilderDelegate = ((PsiBuilderImpl)myDelegate); |
| |
| LOG.assertTrue(myBuilderDelegate.getLexer() instanceof MasqueradingLexer); |
| myLexer = ((MasqueradingLexer)myBuilderDelegate.getLexer()); |
| |
| initShrunkSequence(); |
| } |
| |
| @Override |
| public CharSequence getOriginalText() { |
| return myShrunkCharSequence; |
| } |
| |
| @Override |
| public void advanceLexer() { |
| myLexPosition++; |
| skipWhitespace(); |
| |
| synchronizePositions(false); |
| } |
| |
| /** |
| * @param exact if true then positions should be equal; |
| * else delegate should be behind, not including exactly all foreign (skipped) or whitespace tokens |
| */ |
| private void synchronizePositions(boolean exact) { |
| final PsiBuilder delegate = getDelegate(); |
| |
| if (myLexPosition >= myShrunkSequence.size() || delegate.eof()) { |
| myLexPosition = myShrunkSequence.size(); |
| while (!delegate.eof()) { |
| delegate.advanceLexer(); |
| } |
| return; |
| } |
| |
| if (delegate.getCurrentOffset() > myShrunkSequence.get(myLexPosition).realStart) { |
| LOG.error("delegate is ahead of my builder!"); |
| return; |
| } |
| |
| final int keepUpPosition = getKeepUpPosition(exact); |
| |
| while (!delegate.eof()) { |
| final int delegatePosition = delegate.getCurrentOffset(); |
| |
| if (delegatePosition < keepUpPosition) { |
| delegate.advanceLexer(); |
| } |
| else { |
| break; |
| } |
| } |
| } |
| |
| private int getKeepUpPosition(boolean exact) { |
| if (exact) { |
| return myShrunkSequence.get(myLexPosition).realStart; |
| } |
| |
| int lexPosition = myLexPosition; |
| while (lexPosition > 0 && (myShrunkSequence.get(lexPosition - 1).shrunkStart == myShrunkSequence.get(lexPosition).shrunkStart |
| || isWhiteSpaceOnPos(lexPosition - 1))) { |
| lexPosition--; |
| } |
| if (lexPosition == 0) { |
| return myShrunkSequence.get(lexPosition).realStart; |
| } |
| return myShrunkSequence.get(lexPosition - 1).realStart + 1; |
| } |
| |
| @Override |
| public IElementType lookAhead(int steps) { |
| if (eof()) { // ensure we skip over whitespace if it's needed |
| return null; |
| } |
| int cur = myLexPosition; |
| |
| while (steps > 0) { |
| ++cur; |
| while (cur < myShrunkSequence.size() && isWhiteSpaceOnPos(cur)) { |
| cur++; |
| } |
| |
| steps--; |
| } |
| |
| return cur < myShrunkSequence.size() ? myShrunkSequence.get(cur).elementType : null; |
| } |
| |
| @Override |
| public IElementType rawLookup(int steps) { |
| int cur = myLexPosition + steps; |
| return cur >= 0 && cur < myShrunkSequence.size() ? myShrunkSequence.get(cur).elementType : null; |
| } |
| |
| @Override |
| public int rawTokenTypeStart(int steps) { |
| int cur = myLexPosition + steps; |
| if (cur < 0) return -1; |
| if (cur >= myShrunkSequence.size()) return getOriginalText().length(); |
| return myShrunkSequence.get(cur).shrunkStart; |
| } |
| |
| @Override |
| public int rawTokenIndex() { |
| return myLexPosition; |
| } |
| |
| @Override |
| public int getCurrentOffset() { |
| return myLexPosition < myShrunkSequence.size() ? myShrunkSequence.get(myLexPosition).shrunkStart : myShrunkCharSequence.length(); |
| } |
| |
| @Nullable |
| @Override |
| public IElementType getTokenType() { |
| if (allIsEmpty()) { |
| return TokenType.DUMMY_HOLDER; |
| } |
| skipWhitespace(); |
| |
| return myLexPosition < myShrunkSequence.size() ? myShrunkSequence.get(myLexPosition).elementType : null; |
| } |
| |
| @Nullable |
| @Override |
| public String getTokenText() { |
| if (allIsEmpty()) { |
| return getDelegate().getOriginalText().toString(); |
| } |
| skipWhitespace(); |
| |
| if (myLexPosition >= myShrunkSequence.size()) { |
| return null; |
| } |
| |
| final MyShiftedToken token = myShrunkSequence.get(myLexPosition); |
| return myShrunkCharSequence.subSequence(token.shrunkStart, token.shrunkEnd).toString(); |
| } |
| |
| @Override |
| public boolean eof() { |
| boolean isEof = myLexPosition >= myShrunkSequence.size(); |
| if (!isEof) { |
| return false; |
| } |
| |
| synchronizePositions(true); |
| return true; |
| } |
| |
| @Override |
| public Marker mark() { |
| // In the case of the topmost node all should be inserted |
| if (myLexPosition != 0) { |
| synchronizePositions(true); |
| } |
| |
| final Marker mark = super.mark(); |
| return new MyMarker(mark, myLexPosition); |
| } |
| |
| private boolean allIsEmpty() { |
| return myShrunkSequence.isEmpty() && getDelegate().getOriginalText().length() != 0; |
| } |
| |
| private void skipWhitespace() { |
| while (myLexPosition < myShrunkSequence.size() && isWhiteSpaceOnPos(myLexPosition)) { |
| myLexPosition++; |
| } |
| } |
| |
| private boolean isWhiteSpaceOnPos(int pos) { |
| return myBuilderDelegate.whitespaceOrComment(myShrunkSequence.get(pos).elementType); |
| } |
| |
| protected void initShrunkSequence() { |
| initTokenListAndCharSequence(myLexer); |
| myLexPosition = 0; |
| } |
| |
| private void initTokenListAndCharSequence(MasqueradingLexer lexer) { |
| lexer.start(getDelegate().getOriginalText()); |
| myShrunkSequence = new ArrayList<MyShiftedToken>(); |
| StringBuilder charSequenceBuilder = new StringBuilder(); |
| |
| int realPos = 0; |
| int shrunkPos = 0; |
| while (lexer.getTokenType() != null) { |
| final IElementType masqueTokenType = lexer.getMasqueTokenType(); |
| final String masqueTokenText = lexer.getMasqueTokenText(); |
| |
| final int realLength = lexer.getTokenEnd() - lexer.getTokenStart(); |
| if (masqueTokenType != null) { |
| assert masqueTokenText != null; |
| |
| final int masqueLength = masqueTokenText.length(); |
| myShrunkSequence.add(new MyShiftedToken(masqueTokenType, |
| realPos, realPos + realLength, |
| shrunkPos, shrunkPos + masqueLength)); |
| charSequenceBuilder.append(masqueTokenText); |
| |
| shrunkPos += masqueLength; |
| } |
| realPos += realLength; |
| |
| lexer.advance(); |
| } |
| |
| myShrunkCharSequence = charSequenceBuilder.toString(); |
| } |
| |
| @SuppressWarnings({"StringConcatenationInsideStringBufferAppend", "UnusedDeclaration"}) |
| private void logPos() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("\nmyLexPosition=" + myLexPosition + "/" + myShrunkSequence.size()); |
| if (myLexPosition < myShrunkSequence.size()) { |
| final MyShiftedToken token = myShrunkSequence.get(myLexPosition); |
| sb.append("\nshrunk:" + token.shrunkStart + "," + token.shrunkEnd); |
| sb.append("\nreal:" + token.realStart + "," + token.realEnd); |
| sb.append("\nTT:" + getTokenText()); |
| } |
| sb.append("\ndelegate:"); |
| sb.append("eof=" + myDelegate.eof()); |
| if (!myDelegate.eof()) { |
| //noinspection ConstantConditions |
| sb.append("\nposition:" + myDelegate.getCurrentOffset() + "," + (myDelegate.getCurrentOffset() + myDelegate.getTokenText().length())); |
| sb.append("\nTT:" + myDelegate.getTokenText()); |
| } |
| LOG.info(sb.toString()); |
| } |
| |
| |
| private static class MyShiftedToken { |
| public final IElementType elementType; |
| |
| public final int realStart; |
| public final int realEnd; |
| |
| public final int shrunkStart; |
| public final int shrunkEnd; |
| |
| public MyShiftedToken(IElementType elementType, int realStart, int realEnd, int shrunkStart, int shrunkEnd) { |
| this.elementType = elementType; |
| this.realStart = realStart; |
| this.realEnd = realEnd; |
| this.shrunkStart = shrunkStart; |
| this.shrunkEnd = shrunkEnd; |
| } |
| |
| @Override |
| public String toString() { |
| return "MSTk: [" + realStart + ", " + realEnd + "] -> [" + shrunkStart + ", " + shrunkEnd + "]: " + elementType.toString(); |
| } |
| } |
| |
| private class MyMarker extends DelegateMarker { |
| |
| private int myBuilderPosition; |
| |
| public MyMarker(Marker delegate, int builderPosition) { |
| super(delegate); |
| |
| myBuilderPosition = builderPosition; |
| } |
| |
| @Override |
| public void rollbackTo() { |
| super.rollbackTo(); |
| myLexPosition = myBuilderPosition; |
| } |
| |
| @Override |
| public void doneBefore(IElementType type, Marker before) { |
| super.doneBefore(type, getDelegateOrThis(before)); |
| } |
| |
| @Override |
| public void doneBefore(IElementType type, Marker before, String errorMessage) { |
| super.doneBefore(type, getDelegateOrThis(before), errorMessage); |
| } |
| |
| @NotNull |
| private Marker getDelegateOrThis(@NotNull Marker marker) { |
| if (marker instanceof DelegateMarker) { |
| return ((DelegateMarker)marker).getDelegate(); |
| } |
| else { |
| return marker; |
| } |
| } |
| } |
| } |