package glslplugin.lang.parser; import com.intellij.lang.ForeignLeafType; import com.intellij.lang.PsiBuilder; import com.intellij.lang.TokenWrapper; import com.intellij.lang.impl.PsiBuilderAdapter; 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 PsiBuilderAdapter in which each token can be remapped to an arbitrary number of tokens. * * In order to preserve the functionality of getTokenText(), it is necessary to remap tokens * to an instance of {#link com.intellij.lang.TokenWrapper}. * Created by abigail on 30/06/15. */ public class MultiRemapPsiBuilderAdapter extends PsiBuilderAdapter { protected ArrayList<IElementType> waitingTokens = new ArrayList<IElementType>(); public MultiRemapPsiBuilderAdapter(PsiBuilder delegate) { super(delegate); } @Nullable @Override public IElementType getTokenType() { if (waitingTokens.isEmpty()) { return super.getTokenType(); } else if (waitingTokens.get(0) instanceof TokenWrapper) { return ((TokenWrapper) waitingTokens.get(0)).getDelegate(); } return waitingTokens.get(0); } @Nullable @Override public String getTokenText() { if (waitingTokens.isEmpty()) { return super.getTokenText(); } else if (waitingTokens.get(0) instanceof TokenWrapper) { return ((TokenWrapper) waitingTokens.get(0)).getValue(); } return ""; } private static final String[] NO_NAMES = {}; @NotNull public String[] getNamesThroughWhichThisTokenWasRedefined() { if (waitingTokens.isEmpty()) return NO_NAMES; final IElementType type = waitingTokens.get(0); if (type instanceof RedefinedTokenType) { return ((RedefinedTokenType) type).redefinedThrough; } else { return NO_NAMES; } } @Override public void advanceLexer() { if (waitingTokens.isEmpty()) { super.advanceLexer(); } else { // The underlying PsiBuilder doesn't know about the replaced elements and so doesn't know to create // LeafPsiElement instances for all the tokens. // TODO this should use {#link com.intellij.lang.ForeignLeafType}, which is what the waiting token // will probably be, in order to create leaves with valid text. Marker foreignLeaf = super.mark(); foreignLeaf.done(getTokenType()); waitingTokens.remove(0); } } /** @see #remapCurrentToken(List, String), does that but without redefining */ @Override public void remapCurrentToken(IElementType type) { remapCurrentTokenAdvanceLexer(); waitingTokens.add(0, type); } protected void remapCurrentTokenAdvanceLexer(){ advanceLexer(); } public void remapCurrentToken(List<ForeignLeafType> remapToTypes, String remappedThrough) { remapCurrentTokenAdvanceLexer(); final ArrayList<IElementType> tagged = new ArrayList<IElementType>(remapToTypes.size()); for (final ForeignLeafType type : remapToTypes) { final IElementType taggedType; if (type instanceof RedefinedTokenType) { taggedType = ((RedefinedTokenType) type).redefineAlsoThrough(remappedThrough); } else { taggedType = new RedefinedTokenType(type, remappedThrough); } tagged.add(taggedType); } waitingTokens.addAll(0, tagged); } @NotNull @Override public Marker mark() { return new DelegateMarker(super.mark()); } @Override public IElementType lookAhead(int steps) { final Marker lookaheadMark = mark(); try{ for (int i = 0; i < steps; i++) { advanceLexer(); } return getTokenType(); }finally { lookaheadMark.rollbackTo(); } } protected class DelegateMarker extends com.intellij.lang.impl.DelegateMarker { protected ArrayList<IElementType> rollbackWaitingTokens = new ArrayList<IElementType>(); public DelegateMarker(Marker delegate) { super(delegate); //noinspection unchecked rollbackWaitingTokens = (ArrayList<IElementType>) waitingTokens.clone(); } @NotNull @Override public Marker precede() { Marker precedent = super.precede(); if (precedent instanceof DelegateMarker) { ((DelegateMarker) precedent).rollbackWaitingTokens = rollbackWaitingTokens; } return precedent; } @Override public void rollbackTo() { super.rollbackTo(); waitingTokens = rollbackWaitingTokens; } } }