blob: d67ae891d3bf060c3b105c5a309870675882b7f2 [file] [log] [blame]
/*
* 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.lang.impl;
import com.intellij.lang.*;
import com.intellij.lexer.Lexer;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.TokenType;
import com.intellij.psi.impl.source.CharTableImpl;
import com.intellij.psi.impl.source.resolve.FileContextUtil;
import com.intellij.psi.impl.source.text.BlockSupportImpl;
import com.intellij.psi.impl.source.text.DiffLog;
import com.intellij.psi.impl.source.tree.*;
import com.intellij.psi.text.BlockSupport;
import com.intellij.psi.tree.*;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Convertor;
import com.intellij.util.containers.LimitedPool;
import com.intellij.util.containers.Stack;
import com.intellij.util.diff.DiffTreeChangeBuilder;
import com.intellij.util.diff.FlyweightCapableTreeStructure;
import com.intellij.util.diff.ShallowNodeComparator;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author max
*/
public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder {
private static final Logger LOG = Logger.getInstance("#com.intellij.lang.impl.PsiBuilderImpl");
// function stored in PsiBuilderImpl' user data which called during reparse when merge algorithm is not sure what to merge
public static final Key<TripleFunction<ASTNode, LighterASTNode, FlyweightCapableTreeStructure<LighterASTNode>, ThreeState>>
CUSTOM_COMPARATOR = Key.create("CUSTOM_COMPARATOR");
private final Project myProject;
private PsiFile myFile;
private int[] myLexStarts;
private IElementType[] myLexTypes;
private int myCurrentLexeme;
private final MyList myProduction = new MyList();
private final Lexer myLexer;
private final TokenSet myWhitespaces;
private TokenSet myComments;
private CharTable myCharTable;
private final CharSequence myText;
private final char[] myTextArray;
private boolean myDebugMode = false;
private int myLexemeCount = 0;
private boolean myTokenTypeChecked;
private ITokenTypeRemapper myRemapper;
private WhitespaceSkippedCallback myWhitespaceSkippedCallback;
private final ASTNode myOriginalTree;
private final MyTreeStructure myParentLightTree;
private static TokenSet ourAnyLanguageWhitespaceTokens = TokenSet.EMPTY;
private Map<Key, Object> myUserData = null;
private IElementType myCachedTokenType;
private final LimitedPool<StartMarker> START_MARKERS = new LimitedPool<StartMarker>(2000, new LimitedPool.ObjectFactory<StartMarker>() {
@Override
public StartMarker create() {
return new StartMarker();
}
@Override
public void cleanup(final StartMarker startMarker) {
startMarker.clean();
}
});
private final LimitedPool<DoneMarker> DONE_MARKERS = new LimitedPool<DoneMarker>(2000, new LimitedPool.ObjectFactory<DoneMarker>() {
@Override
public DoneMarker create() {
return new DoneMarker();
}
@Override
public void cleanup(final DoneMarker doneMarker) {
doneMarker.clean();
}
});
public static void registerWhitespaceToken(IElementType type) {
ourAnyLanguageWhitespaceTokens = TokenSet.orSet(ourAnyLanguageWhitespaceTokens, TokenSet.create(type));
}
public PsiBuilderImpl(@NotNull Project project,
PsiFile containingFile,
@NotNull ParserDefinition parserDefinition,
@NotNull Lexer lexer,
CharTable charTable,
@NotNull final CharSequence text,
@Nullable ASTNode originalTree,
@Nullable MyTreeStructure parentLightTree) {
this(project, containingFile, parserDefinition.getWhitespaceTokens(), parserDefinition.getCommentTokens(), lexer, charTable, text,
originalTree, parentLightTree);
}
public PsiBuilderImpl(Project project,
PsiFile containingFile,
@NotNull TokenSet whiteSpaces,
@NotNull TokenSet comments,
@NotNull Lexer lexer,
CharTable charTable,
@NotNull final CharSequence text,
@Nullable ASTNode originalTree,
@Nullable MyTreeStructure parentLightTree) {
myProject = project;
myFile = containingFile;
myText = text;
myTextArray = CharArrayUtil.fromSequenceWithoutCopying(text);
myLexer = lexer;
myWhitespaces = whiteSpaces;
myComments = comments;
myCharTable = charTable;
myOriginalTree = originalTree;
myParentLightTree = parentLightTree;
cacheLexemes();
}
public PsiBuilderImpl(@NotNull final Project project,
@NotNull final ParserDefinition parserDefinition,
@NotNull final Lexer lexer,
@NotNull final ASTNode chameleon,
@NotNull final CharSequence text) {
this(project, SharedImplUtil.getContainingFile(chameleon), parserDefinition, lexer, SharedImplUtil.findCharTableByTree(chameleon), text,
chameleon.getUserData(BlockSupport.TREE_TO_BE_REPARSED), null);
}
public PsiBuilderImpl(@NotNull final Project project,
@NotNull final ParserDefinition parserDefinition,
@NotNull final Lexer lexer,
@NotNull final LighterLazyParseableNode chameleon,
@NotNull final CharSequence text) {
this(project, chameleon.getContainingFile(), parserDefinition, lexer, chameleon.getCharTable(), text,
null, ((LazyParseableToken)chameleon).myParent);
}
private void cacheLexemes() {
int approxLexCount = Math.max(10, myText.length() / 5);
myLexStarts = new int[approxLexCount];
myLexTypes = new IElementType[approxLexCount];
myLexer.start(myText);
int i = 0;
int offset = 0;
while (true) {
ProgressIndicatorProvider.checkCanceled();
IElementType type = myLexer.getTokenType();
if (type == null) break;
if (i >= myLexTypes.length - 1) {
resizeLexemes(i * 3 / 2);
}
int tokenStart = myLexer.getTokenStart();
if (tokenStart < offset) {
final StringBuilder sb = new StringBuilder();
final IElementType tokenType = myLexer.getTokenType();
sb.append("Token sequence broken")
.append("\n this: '").append(myLexer.getTokenText()).append("' (").append(tokenType).append(':')
.append(tokenType != null ? tokenType.getLanguage() : null).append(") ").append(tokenStart).append(":").append(myLexer.getTokenEnd());
if (i > 0) {
final int prevStart = myLexStarts[i - 1];
sb.append("\n prev: '").append(myText.subSequence(prevStart, offset)).append("' (").append(myLexTypes[i - 1]).append(':')
.append(myLexTypes[i - 1].getLanguage()).append(") ").append(prevStart).append(":").append(offset);
}
final int quoteStart = Math.max(tokenStart - 256, 0);
final int quoteEnd = Math.min(tokenStart + 256, myText.length());
sb.append("\n quote: [").append(quoteStart).append(':').append(quoteEnd)
.append("] '").append(myText.subSequence(quoteStart, quoteEnd)).append('\'');
LOG.error(sb);
}
myLexStarts[i] = offset = tokenStart;
myLexTypes[i] = type;
i++;
myLexer.advance();
}
myLexStarts[i] = myText.length();
myLexemeCount = i;
clearCachedTokenType();
}
@Override
public Project getProject() {
return myProject;
}
@Override
public void enforceCommentTokens(TokenSet tokens) {
myComments = tokens;
}
@Override
@Nullable
public LighterASTNode getLatestDoneMarker() {
int index = myProduction.size() - 1;
while (index >= 0) {
ProductionMarker marker = myProduction.get(index);
if (marker instanceof DoneMarker) return ((DoneMarker)marker).myStart;
--index;
}
return null;
}
private abstract static class Node implements LighterASTNode {
public abstract int hc();
}
public abstract static class ProductionMarker extends Node {
protected int myLexemeIndex;
protected WhitespacesAndCommentsBinder myEdgeTokenBinder;
protected ProductionMarker myParent;
protected ProductionMarker myNext;
public void clean() {
myLexemeIndex = 0;
myParent = myNext = null;
}
public void remapTokenType(IElementType type) {
throw new UnsupportedOperationException("Shall not be called on this kind of markers");
}
public int getStartIndex() {
return myLexemeIndex;
}
public int getEndIndex() {
throw new UnsupportedOperationException("Shall not be called on this kind of markers");
}
}
private static class StartMarker extends ProductionMarker implements Marker {
private PsiBuilderImpl myBuilder;
private IElementType myType;
private DoneMarker myDoneMarker;
private Throwable myDebugAllocationPosition;
private ProductionMarker myFirstChild;
private ProductionMarker myLastChild;
private int myHC = -1;
private StartMarker() {
myEdgeTokenBinder = WhitespacesBinders.DEFAULT_LEFT_BINDER;
}
@Override
public void clean() {
super.clean();
myBuilder = null;
myType = null;
myDoneMarker = null;
myDebugAllocationPosition = null;
myFirstChild = myLastChild = null;
myHC = -1;
myEdgeTokenBinder = WhitespacesBinders.DEFAULT_LEFT_BINDER;
}
@Override
public int hc() {
if (myHC == -1) {
PsiBuilderImpl builder = myBuilder;
int hc = 0;
final CharSequence buf = builder.myText;
final char[] bufArray = builder.myTextArray;
ProductionMarker child = myFirstChild;
int lexIdx = myLexemeIndex;
while (child != null) {
int lastLeaf = child.myLexemeIndex;
for (int i = builder.myLexStarts[lexIdx]; i < builder.myLexStarts[lastLeaf]; i++) {
hc += bufArray != null ? bufArray[i] : buf.charAt(i);
}
lexIdx = lastLeaf;
hc += child.hc();
if (child instanceof StartMarker) {
lexIdx = ((StartMarker)child).myDoneMarker.myLexemeIndex;
}
child = child.myNext;
}
for (int i = builder.myLexStarts[lexIdx]; i < builder.myLexStarts[myDoneMarker.myLexemeIndex]; i++) {
hc += bufArray != null ? bufArray[i] : buf.charAt(i);
}
myHC = hc;
}
return myHC;
}
@Override
public int getStartOffset() {
return myBuilder.myLexStarts[myLexemeIndex];
}
@Override
public int getEndOffset() {
return myBuilder.myLexStarts[myDoneMarker.myLexemeIndex];
}
@Override
public int getEndIndex() {
return myDoneMarker.myLexemeIndex;
}
public void addChild(ProductionMarker node) {
if (myFirstChild == null) {
myFirstChild = node;
myLastChild = node;
}
else {
myLastChild.myNext = node;
myLastChild = node;
}
}
@Override
public Marker precede() {
return myBuilder.precede(this);
}
@Override
public void drop() {
myBuilder.drop(this);
}
@Override
public void rollbackTo() {
myBuilder.rollbackTo(this);
}
@Override
public void done(IElementType type) {
myType = type;
myBuilder.done(this);
}
@Override
public void collapse(IElementType type) {
myType = type;
myBuilder.collapse(this);
}
@Override
public void doneBefore(IElementType type, Marker before) {
myType = type;
myBuilder.doneBefore(this, before);
}
@Override
public void doneBefore(final IElementType type, final Marker before, final String errorMessage) {
final StartMarker marker = (StartMarker)before;
myBuilder.myProduction.add(myBuilder.myProduction.lastIndexOf(marker),
new ErrorItem(myBuilder, errorMessage, marker.myLexemeIndex));
doneBefore(type, before);
}
@Override
public void error(String message) {
myType = TokenType.ERROR_ELEMENT;
myBuilder.error(this, message);
}
@Override
public void errorBefore(final String message, final Marker before) {
myType = TokenType.ERROR_ELEMENT;
myBuilder.errorBefore(this, message, before);
}
@Override
public IElementType getTokenType() {
return myType;
}
@Override
public void remapTokenType(IElementType type) {
//assert myType != null && type != null;
myType = type;
}
@Override
public void setCustomEdgeTokenBinders(final WhitespacesAndCommentsBinder left, final WhitespacesAndCommentsBinder right) {
if (left != null) {
myEdgeTokenBinder = left;
}
if (right != null) {
if (myDoneMarker == null) throw new IllegalArgumentException("Cannot set right-edge processor for unclosed marker");
myDoneMarker.myEdgeTokenBinder = right;
}
}
}
private Marker precede(final StartMarker marker) {
int idx = myProduction.lastIndexOf(marker);
if (idx < 0) {
LOG.error("Cannot precede dropped or rolled-back marker");
}
StartMarker pre = createMarker(marker.myLexemeIndex);
myProduction.add(idx, pre);
return pre;
}
private abstract static class Token extends Node {
protected PsiBuilderImpl myBuilder;
private IElementType myTokenType;
private int myTokenStart;
private int myTokenEnd;
private int myHC = -1;
public void clean() {
myBuilder = null;
myHC = -1;
}
@Override
public int hc() {
if (myHC == -1) {
int hc = 0;
if (myTokenType instanceof TokenWrapper) {
final String value = ((TokenWrapper)myTokenType).getValue();
for (int i = 0; i < value.length(); i++) {
hc += value.charAt(i);
}
}
else {
final int start = myTokenStart;
final int end = myTokenEnd;
final CharSequence buf = myBuilder.myText;
final char[] bufArray = myBuilder.myTextArray;
for (int i = start; i < end; i++) {
hc += bufArray != null ? bufArray[i] : buf.charAt(i);
}
}
myHC = hc;
}
return myHC;
}
@Override
public int getEndOffset() {
return myTokenEnd;
}
@Override
public int getStartOffset() {
return myTokenStart;
}
public CharSequence getText() {
if (myTokenType instanceof TokenWrapper) {
return ((TokenWrapper)myTokenType).getValue();
}
return myBuilder.myText.subSequence(myTokenStart, myTokenEnd);
}
@Override
public IElementType getTokenType() {
return myTokenType;
}
}
private static class TokenNode extends Token implements LighterASTTokenNode {
}
private static class LazyParseableToken extends Token implements LighterLazyParseableNode {
private MyTreeStructure myParent;
private FlyweightCapableTreeStructure<LighterASTNode> myParsed;
private int myStartIndex;
private int myEndIndex;
@Override
public void clean() {
super.clean();
myParent = null;
myParsed = null;
}
@Override
public PsiFile getContainingFile() {
return myBuilder.myFile;
}
@Override
public CharTable getCharTable() {
return myBuilder.myCharTable;
}
public FlyweightCapableTreeStructure<LighterASTNode> parseContents() {
if (myParsed == null) {
myParsed = ((ILightLazyParseableElementType)getTokenType()).parseContents(this);
}
return myParsed;
}
@Override
public boolean accept(@NotNull Visitor visitor) {
for (int i = myStartIndex; i < myEndIndex; i++) {
IElementType type = myBuilder.myLexTypes[i];
if (!visitor.visit(type)) {
return false;
}
}
return true;
}
}
private static class DoneMarker extends ProductionMarker {
private StartMarker myStart;
private boolean myCollapse = false;
public DoneMarker() {
myEdgeTokenBinder = WhitespacesBinders.DEFAULT_RIGHT_BINDER;
}
public DoneMarker(final StartMarker marker, final int currentLexeme) {
this();
myLexemeIndex = currentLexeme;
myStart = marker;
}
@Override
public void clean() {
super.clean();
myStart = null;
myEdgeTokenBinder = WhitespacesBinders.DEFAULT_RIGHT_BINDER;
}
@Override
public int hc() {
throw new UnsupportedOperationException("Shall not be called on this kind of markers");
}
@Override
public IElementType getTokenType() {
throw new UnsupportedOperationException("Shall not be called on this kind of markers");
}
@Override
public int getEndOffset() {
throw new UnsupportedOperationException("Shall not be called on this kind of markers");
}
@Override
public int getStartOffset() {
throw new UnsupportedOperationException("Shall not be called on this kind of markers");
}
}
private static class DoneWithErrorMarker extends DoneMarker {
private String myMessage;
public DoneWithErrorMarker(final StartMarker marker, final int currentLexeme, final String message) {
super(marker, currentLexeme);
myMessage = message;
}
@Override
public void clean() {
super.clean();
myMessage = null;
}
}
private static class ErrorItem extends ProductionMarker {
private final PsiBuilderImpl myBuilder;
private String myMessage;
public ErrorItem(final PsiBuilderImpl builder, final String message, final int idx) {
myBuilder = builder;
myMessage = message;
myLexemeIndex = idx;
myEdgeTokenBinder = WhitespacesBinders.DEFAULT_RIGHT_BINDER;
}
@Override
public void clean() {
super.clean();
myMessage = null;
}
@Override
public int hc() {
return 0;
}
@Override
public int getEndOffset() {
return myBuilder.myLexStarts[myLexemeIndex];
}
@Override
public int getStartOffset() {
return myBuilder.myLexStarts[myLexemeIndex];
}
@Override
public IElementType getTokenType() {
return TokenType.ERROR_ELEMENT;
}
}
@Override
public CharSequence getOriginalText() {
return myText;
}
@Override
@Nullable
public IElementType getTokenType() {
IElementType cached = myCachedTokenType;
if (cached == null) {
myCachedTokenType = cached = calcTokenType();
}
return cached;
}
private void clearCachedTokenType() {
myCachedTokenType = null;
}
private IElementType calcTokenType() {
if (eof()) return null;
if (myRemapper != null) {
IElementType type = myLexTypes[myCurrentLexeme];
type = myRemapper.filter(type, myLexStarts[myCurrentLexeme], myLexStarts[myCurrentLexeme + 1], myLexer.getBufferSequence());
myLexTypes[myCurrentLexeme] = type; // filter may have changed the type
skipWhitespace();
type = myLexTypes[myCurrentLexeme];
return type;
}
return myLexTypes[myCurrentLexeme];
}
@Override
public void setTokenTypeRemapper(final ITokenTypeRemapper remapper) {
myRemapper = remapper;
clearCachedTokenType();
}
@Override
public void remapCurrentToken(IElementType type) {
myLexTypes[myCurrentLexeme] = type;
clearCachedTokenType();
}
@Nullable
@Override
public IElementType lookAhead(int steps) {
if (eof()) { // ensure we skip over whitespace if it's needed
return null;
}
int cur = myCurrentLexeme;
while (steps > 0) {
++cur;
while (cur < myLexemeCount && whitespaceOrComment(myLexTypes[cur])) {
cur++;
}
steps--;
}
return cur < myLexemeCount ? myLexTypes[cur] : null;
}
@Override
public IElementType rawLookup(int steps) {
int cur = myCurrentLexeme + steps;
return cur < myLexemeCount && cur >= 0 ? myLexTypes[cur] : null;
}
@Override
public int rawTokenTypeStart(int steps) {
int cur = myCurrentLexeme + steps;
if (cur < 0) return -1;
if (cur >= myLexemeCount) return getOriginalText().length();
return myLexStarts[cur];
}
@Override
public int rawTokenIndex() {
return myCurrentLexeme;
}
public int rawTokenOffset(int tokenIndex) {
return myLexStarts[tokenIndex];
}
@Override
public void setWhitespaceSkippedCallback(@Nullable final WhitespaceSkippedCallback callback) {
myWhitespaceSkippedCallback = callback;
}
@Override
public void advanceLexer() {
if (eof()) return;
if (!myTokenTypeChecked) {
LOG.assertTrue(eof(), "Probably a bug: eating token without its type checking");
}
myTokenTypeChecked = false;
myCurrentLexeme++;
ProgressIndicatorProvider.checkCanceled();
clearCachedTokenType();
}
private void skipWhitespace() {
while (myCurrentLexeme < myLexemeCount && whitespaceOrComment(myLexTypes[myCurrentLexeme])) {
onSkip(myLexTypes[myCurrentLexeme], myLexStarts[myCurrentLexeme], myCurrentLexeme + 1 < myLexemeCount ? myLexStarts[myCurrentLexeme + 1] : myText.length());
myCurrentLexeme++;
clearCachedTokenType();
}
}
private void onSkip(IElementType type, int start, int end) {
if (myWhitespaceSkippedCallback != null) {
myWhitespaceSkippedCallback.onSkip(type, start, end);
}
}
@Override
public int getCurrentOffset() {
if (eof()) return getOriginalText().length();
return myLexStarts[myCurrentLexeme];
}
@Override
@Nullable
public String getTokenText() {
if (eof()) return null;
final IElementType type = getTokenType();
if (type instanceof TokenWrapper) {
return ((TokenWrapper)type).getValue();
}
return myText.subSequence(myLexStarts[myCurrentLexeme], myLexStarts[myCurrentLexeme + 1]).toString();
}
private void resizeLexemes(final int newSize) {
int count = Math.min(newSize, myLexTypes.length);
int[] newStarts = new int[newSize + 1];
System.arraycopy(myLexStarts, 0, newStarts, 0, count);
myLexStarts = newStarts;
IElementType[] newTypes = new IElementType[newSize];
System.arraycopy(myLexTypes, 0, newTypes, 0, count);
myLexTypes = newTypes;
clearCachedTokenType();
}
public boolean whitespaceOrComment(IElementType token) {
return myWhitespaces.contains(token) || myComments.contains(token);
}
@Override
public Marker mark() {
if (!myProduction.isEmpty()) {
skipWhitespace();
}
StartMarker marker = createMarker(myCurrentLexeme);
myProduction.add(marker);
return marker;
}
private StartMarker createMarker(final int lexemeIndex) {
StartMarker marker = START_MARKERS.alloc();
marker.myLexemeIndex = lexemeIndex;
marker.myBuilder = this;
if (myDebugMode) {
marker.myDebugAllocationPosition = new Throwable("Created at the following trace.");
}
return marker;
}
@Override
public final boolean eof() {
if (!myTokenTypeChecked) {
myTokenTypeChecked = true;
skipWhitespace();
}
return myCurrentLexeme >= myLexemeCount;
}
@SuppressWarnings({"SuspiciousMethodCalls"})
private void rollbackTo(Marker marker) {
myCurrentLexeme = ((StartMarker)marker).myLexemeIndex;
myTokenTypeChecked = true;
int idx = myProduction.lastIndexOf(marker);
if (idx < 0) {
LOG.error("The marker must be added before rolled back to.");
}
myProduction.removeRange(idx, myProduction.size());
START_MARKERS.recycle((StartMarker)marker);
clearCachedTokenType();
}
@SuppressWarnings({"SuspiciousMethodCalls"})
public void drop(Marker marker) {
final DoneMarker doneMarker = ((StartMarker)marker).myDoneMarker;
if (doneMarker != null) {
myProduction.remove(myProduction.lastIndexOf(doneMarker));
DONE_MARKERS.recycle(doneMarker);
}
final boolean removed = myProduction.remove(myProduction.lastIndexOf(marker)) == marker;
if (!removed) {
LOG.error("The marker must be added before it is dropped.");
}
START_MARKERS.recycle((StartMarker)marker);
}
public void error(Marker marker, String message) {
doValidityChecks(marker, null);
DoneWithErrorMarker doneMarker = new DoneWithErrorMarker((StartMarker)marker, myCurrentLexeme, message);
boolean tieToTheLeft = isEmpty(((StartMarker)marker).myLexemeIndex, myCurrentLexeme);
if (tieToTheLeft) ((StartMarker)marker).myEdgeTokenBinder = WhitespacesBinders.DEFAULT_RIGHT_BINDER;
((StartMarker)marker).myDoneMarker = doneMarker;
myProduction.add(doneMarker);
}
@SuppressWarnings({"SuspiciousMethodCalls"})
public void errorBefore(Marker marker, String message, Marker before) {
doValidityChecks(marker, before);
int beforeIndex = myProduction.lastIndexOf(before);
DoneWithErrorMarker doneMarker = new DoneWithErrorMarker((StartMarker)marker, ((StartMarker)before).myLexemeIndex, message);
boolean tieToTheLeft = isEmpty(((StartMarker)marker).myLexemeIndex, ((StartMarker)before).myLexemeIndex);
if (tieToTheLeft) ((StartMarker)marker).myEdgeTokenBinder = WhitespacesBinders.DEFAULT_RIGHT_BINDER;
((StartMarker)marker).myDoneMarker = doneMarker;
myProduction.add(beforeIndex, doneMarker);
}
public void done(final Marker marker) {
doValidityChecks(marker, null);
DoneMarker doneMarker = DONE_MARKERS.alloc();
doneMarker.myStart = (StartMarker)marker;
doneMarker.myLexemeIndex = myCurrentLexeme;
boolean tieToTheLeft = doneMarker.myStart.myType.isLeftBound() &&
isEmpty(((StartMarker)marker).myLexemeIndex, myCurrentLexeme);
if (tieToTheLeft) ((StartMarker)marker).myEdgeTokenBinder = WhitespacesBinders.DEFAULT_RIGHT_BINDER;
((StartMarker)marker).myDoneMarker = doneMarker;
myProduction.add(doneMarker);
}
@SuppressWarnings({"SuspiciousMethodCalls"})
public void doneBefore(Marker marker, Marker before) {
doValidityChecks(marker, before);
int beforeIndex = myProduction.lastIndexOf(before);
DoneMarker doneMarker = DONE_MARKERS.alloc();
doneMarker.myLexemeIndex = ((StartMarker)before).myLexemeIndex;
doneMarker.myStart = (StartMarker)marker;
boolean tieToTheLeft = doneMarker.myStart.myType.isLeftBound() &&
isEmpty(((StartMarker)marker).myLexemeIndex, ((StartMarker)before).myLexemeIndex);
if (tieToTheLeft) ((StartMarker)marker).myEdgeTokenBinder = WhitespacesBinders.DEFAULT_RIGHT_BINDER;
((StartMarker)marker).myDoneMarker = doneMarker;
myProduction.add(beforeIndex, doneMarker);
}
private boolean isEmpty(final int startIdx, final int endIdx) {
for (int i = startIdx; i < endIdx; i++) {
final IElementType token = myLexTypes[i];
if (!whitespaceOrComment(token)) return false;
}
return true;
}
public void collapse(final Marker marker) {
done(marker);
((StartMarker)marker).myDoneMarker.myCollapse = true;
}
@SuppressWarnings({"UseOfSystemOutOrSystemErr", "SuspiciousMethodCalls", "ThrowableResultOfMethodCallIgnored"})
private void doValidityChecks(final Marker marker, @Nullable final Marker before) {
final DoneMarker doneMarker = ((StartMarker)marker).myDoneMarker;
if (doneMarker != null) {
LOG.error("Marker already done.");
}
if (!myDebugMode) return;
int idx = myProduction.lastIndexOf(marker);
if (idx < 0) {
LOG.error("Marker has never been added.");
}
int endIdx = myProduction.size();
if (before != null) {
endIdx = myProduction.lastIndexOf(before);
if (endIdx < 0) {
LOG.error("'Before' marker has never been added.");
}
if (idx > endIdx) {
LOG.error("'Before' marker precedes this one.");
}
}
for (int i = endIdx - 1; i > idx; i--) {
Object item = myProduction.get(i);
if (item instanceof StartMarker) {
StartMarker otherMarker = (StartMarker)item;
if (otherMarker.myDoneMarker == null) {
final Throwable debugAllocOther = otherMarker.myDebugAllocationPosition;
final Throwable debugAllocThis = ((StartMarker)marker).myDebugAllocationPosition;
if (debugAllocOther != null) {
Throwable currentTrace = new Throwable();
ExceptionUtil.makeStackTraceRelative(debugAllocThis, currentTrace).printStackTrace(System.err);
ExceptionUtil.makeStackTraceRelative(debugAllocOther, currentTrace).printStackTrace(System.err);
}
LOG.error("Another not done marker added after this one. Must be done before this.");
}
}
}
}
@Override
public void error(String messageText) {
final ProductionMarker lastMarker = myProduction.get(myProduction.size() - 1);
if (lastMarker instanceof ErrorItem && lastMarker.myLexemeIndex == myCurrentLexeme) {
return;
}
myProduction.add(new ErrorItem(this, messageText, myCurrentLexeme));
}
@Override
public ASTNode getTreeBuilt() {
try {
return buildTree();
}
finally {
for (ProductionMarker marker : myProduction) {
if (marker instanceof StartMarker) {
START_MARKERS.recycle((StartMarker)marker);
}
else if (marker instanceof DoneMarker) {
DONE_MARKERS.recycle((DoneMarker)marker);
}
}
}
}
private ASTNode buildTree() {
final StartMarker rootMarker = prepareLightTree();
final boolean isTooDeep = myFile != null && BlockSupport.isTooDeep(myFile.getOriginalFile());
if (myOriginalTree != null && !isTooDeep) {
DiffLog diffLog = merge(myOriginalTree, rootMarker);
throw new BlockSupport.ReparsedSuccessfullyException(diffLog);
}
final ASTNode rootNode = createRootAST(rootMarker);
bind(rootMarker, (CompositeElement)rootNode);
if (isTooDeep && !(rootNode instanceof FileElement)) {
final ASTNode childNode = rootNode.getFirstChildNode();
childNode.putUserData(BlockSupport.TREE_DEPTH_LIMIT_EXCEEDED, Boolean.TRUE);
}
return rootNode;
}
@Override
public FlyweightCapableTreeStructure<LighterASTNode> getLightTree() {
final StartMarker rootMarker = prepareLightTree();
return new MyTreeStructure(rootMarker, myParentLightTree);
}
private ASTNode createRootAST(final StartMarker rootMarker) {
final IElementType type = rootMarker.getTokenType();
@SuppressWarnings("NullableProblems")
final ASTNode rootNode = type instanceof ILazyParseableElementType ?
ASTFactory.lazy((ILazyParseableElementType)type, null) : createComposite(rootMarker);
if (myCharTable == null) {
myCharTable = rootNode instanceof FileElement ? ((FileElement)rootNode).getCharTable() : new CharTableImpl();
}
if (!(rootNode instanceof FileElement)) {
rootNode.putUserData(CharTable.CHAR_TABLE_KEY, myCharTable);
}
return rootNode;
}
private static class ConvertFromTokensToASTBuilder implements DiffTreeChangeBuilder<ASTNode, LighterASTNode> {
private final DiffTreeChangeBuilder<ASTNode, ASTNode> myDelegate;
private final ASTConverter myConverter;
public ConvertFromTokensToASTBuilder(StartMarker rootNode, DiffTreeChangeBuilder<ASTNode, ASTNode> delegate) {
myDelegate = delegate;
myConverter = new ASTConverter(rootNode);
}
@Override
public void nodeDeleted(@NotNull final ASTNode oldParent, @NotNull final ASTNode oldNode) {
myDelegate.nodeDeleted(oldParent, oldNode);
}
@Override
public void nodeInserted(@NotNull final ASTNode oldParent, @NotNull final LighterASTNode newNode, final int pos) {
myDelegate.nodeInserted(oldParent, myConverter.convert((Node)newNode), pos);
}
@Override
public void nodeReplaced(@NotNull final ASTNode oldChild, @NotNull final LighterASTNode newChild) {
ASTNode converted = myConverter.convert((Node)newChild);
myDelegate.nodeReplaced(oldChild, converted);
}
}
@NonNls private static final String UNBALANCED_MESSAGE =
"Unbalanced tree. Most probably caused by unbalanced markers. " +
"Try calling setDebugMode(true) against PsiBuilder passed to identify exact location of the problem";
@NotNull
private DiffLog merge(@NotNull final ASTNode oldRoot, @NotNull StartMarker newRoot) {
DiffLog diffLog = new DiffLog();
final ConvertFromTokensToASTBuilder builder = new ConvertFromTokensToASTBuilder(newRoot, diffLog);
final MyTreeStructure treeStructure = new MyTreeStructure(newRoot, null);
final MyComparator comparator = new MyComparator(getUserDataUnprotected(CUSTOM_COMPARATOR), treeStructure);
final ProgressIndicator indicator = ProgressIndicatorProvider.getGlobalProgressIndicator();
BlockSupportImpl.diffTrees(oldRoot, builder, comparator, treeStructure, indicator);
return diffLog;
}
@NotNull
private StartMarker prepareLightTree() {
myTokenTypeChecked = true;
balanceWhiteSpaces();
if (myProduction.isEmpty()) {
LOG.error("Parser produced no markers. Text:\n" + myText);
}
final StartMarker rootMarker = (StartMarker)myProduction.get(0);
rootMarker.myParent = rootMarker.myFirstChild = rootMarker.myLastChild = rootMarker.myNext = null;
StartMarker curNode = rootMarker;
final Stack<StartMarker> nodes = ContainerUtil.newStack();
nodes.push(rootMarker);
@SuppressWarnings({"MultipleVariablesInDeclaration"}) int lastErrorIndex = -1, maxDepth = 0, curDepth = 0;
for (int i = 1; i < myProduction.size(); i++) {
final ProductionMarker item = myProduction.get(i);
if (curNode == null) LOG.error("Unexpected end of the production");
item.myParent = curNode;
if (item instanceof StartMarker) {
final StartMarker marker = (StartMarker)item;
marker.myFirstChild = marker.myLastChild = marker.myNext = null;
curNode.addChild(marker);
nodes.push(curNode);
curNode = marker;
curDepth++;
if (curDepth > maxDepth) maxDepth = curDepth;
}
else if (item instanceof DoneMarker) {
assertMarkersBalanced(((DoneMarker)item).myStart == curNode, item);
curNode = nodes.pop();
curDepth--;
}
else if (item instanceof ErrorItem) {
int curToken = item.myLexemeIndex;
if (curToken == lastErrorIndex) continue;
lastErrorIndex = curToken;
curNode.addChild(item);
}
}
if (myCurrentLexeme < myLexemeCount) {
final List<IElementType> missed = ContainerUtil.newArrayList(myLexTypes, myCurrentLexeme, myLexemeCount);
LOG.error("Tokens " + missed + " were not inserted into the tree. " +(myFile != null? myFile.getLanguage()+", ":"")+"Text:\n" + myText);
}
if (rootMarker.myDoneMarker.myLexemeIndex < myLexemeCount) {
final List<IElementType> missed = ContainerUtil.newArrayList(myLexTypes, rootMarker.myDoneMarker.myLexemeIndex, myLexemeCount);
LOG.error("Tokens " + missed + " are outside of root element \"" + rootMarker.myType + "\". Text:\n" + myText);
}
if (myLexStarts.length <= myCurrentLexeme + 1) {
resizeLexemes(myCurrentLexeme + 1);
}
myLexStarts[myCurrentLexeme] = myText.length(); // $ terminating token.;
myLexStarts[myCurrentLexeme + 1] = 0;
myLexTypes[myCurrentLexeme] = null;
assertMarkersBalanced(curNode == rootMarker, curNode);
checkTreeDepth(maxDepth, rootMarker.getTokenType() instanceof IFileElementType);
clearCachedTokenType();
return rootMarker;
}
private void assertMarkersBalanced(boolean condition, @Nullable ProductionMarker marker) {
if (condition) return;
int index = marker != null ? marker.getStartIndex() + 1 : myLexStarts.length;
CharSequence context =
index < myLexStarts.length ? myText.subSequence(Math.max(0, myLexStarts[index] - 1000), myLexStarts[index]) : "<none>";
String language = myFile != null ? myFile.getLanguage() + ", " : "";
LOG.error(UNBALANCED_MESSAGE + "\n" + language + context);
}
private void balanceWhiteSpaces() {
RelativeTokenTypesView wsTokens = new RelativeTokenTypesView();
RelativeTokenTextView tokenTextGetter = new RelativeTokenTextView();
int lastIndex = 0;
for (int i = 1, size = myProduction.size() - 1; i < size; i++) {
ProductionMarker item = myProduction.get(i);
if (item instanceof StartMarker) {
assertMarkersBalanced(((StartMarker)item).myDoneMarker != null, item);
}
int prevProductionLexIndex = myProduction.get(i - 1).myLexemeIndex;
int wsStartIndex = Math.max(item.myLexemeIndex, lastIndex);
while (wsStartIndex > prevProductionLexIndex && whitespaceOrComment(myLexTypes[wsStartIndex - 1])) wsStartIndex--;
int wsEndIndex = item.myLexemeIndex;
while (wsEndIndex < myLexemeCount && whitespaceOrComment(myLexTypes[wsEndIndex])) wsEndIndex++;
if (wsStartIndex != wsEndIndex) {
wsTokens.configure(wsStartIndex, wsEndIndex);
tokenTextGetter.configure(wsStartIndex);
boolean atEnd = wsStartIndex == 0 || wsEndIndex == myLexemeCount;
item.myLexemeIndex = wsStartIndex + item.myEdgeTokenBinder.getEdgePosition(wsTokens, atEnd, tokenTextGetter);
}
else if (item.myLexemeIndex < wsStartIndex) {
item.myLexemeIndex = wsStartIndex;
}
lastIndex = item.myLexemeIndex;
}
}
private final class RelativeTokenTypesView extends AbstractList<IElementType> {
private int myStart;
private int mySize;
private void configure(int start, int end) {
myStart = start;
mySize = end - start;
}
@Override
public IElementType get(int index) {
return myLexTypes[myStart + index];
}
@Override
public int size() {
return mySize;
}
}
private final class RelativeTokenTextView implements WhitespacesAndCommentsBinder.TokenTextGetter {
private int myStart;
private void configure(int start) {
myStart = start;
}
@Override
public CharSequence get(int i) {
return myText.subSequence(myLexStarts[myStart + i], myLexStarts[myStart + i + 1]);
}
}
private void checkTreeDepth(final int maxDepth, final boolean isFileRoot) {
if (myFile == null) return;
final PsiFile file = myFile.getOriginalFile();
final Boolean flag = file.getUserData(BlockSupport.TREE_DEPTH_LIMIT_EXCEEDED);
if (maxDepth > BlockSupport.INCREMENTAL_REPARSE_DEPTH_LIMIT) {
if (!Boolean.TRUE.equals(flag)) {
file.putUserData(BlockSupport.TREE_DEPTH_LIMIT_EXCEEDED, Boolean.TRUE);
}
}
else if (isFileRoot && flag != null) {
file.putUserData(BlockSupport.TREE_DEPTH_LIMIT_EXCEEDED, null);
}
}
private void bind(final StartMarker rootMarker, final CompositeElement rootNode) {
StartMarker curMarker = rootMarker;
CompositeElement curNode = rootNode;
int lexIndex = rootMarker.myLexemeIndex;
ProductionMarker item = rootMarker.myFirstChild != null ? rootMarker.myFirstChild : rootMarker.myDoneMarker;
while (true) {
lexIndex = insertLeaves(lexIndex, item.myLexemeIndex, curNode);
if (item == rootMarker.myDoneMarker) break;
if (item instanceof StartMarker) {
final StartMarker marker = (StartMarker)item;
if (!marker.myDoneMarker.myCollapse) {
curMarker = marker;
final CompositeElement childNode = createComposite(marker);
curNode.rawAddChildrenWithoutNotifications(childNode);
curNode = childNode;
item = marker.myFirstChild != null ? marker.myFirstChild : marker.myDoneMarker;
continue;
}
else {
lexIndex = collapseLeaves(curNode, marker);
}
}
else if (item instanceof ErrorItem) {
final CompositeElement errorElement = Factory.createErrorElement(((ErrorItem)item).myMessage);
curNode.rawAddChildrenWithoutNotifications(errorElement);
}
else if (item instanceof DoneMarker) {
curMarker = (StartMarker)((DoneMarker)item).myStart.myParent;
curNode = curNode.getTreeParent();
item = ((DoneMarker)item).myStart;
}
item = item.myNext != null ? item.myNext : curMarker.myDoneMarker;
}
}
private int insertLeaves(int curToken, int lastIdx, final CompositeElement curNode) {
lastIdx = Math.min(lastIdx, myLexemeCount);
while (curToken < lastIdx) {
ProgressIndicatorProvider.checkCanceled();
final int start = myLexStarts[curToken];
final int end = myLexStarts[curToken + 1];
if (start < end || myLexTypes[curToken] instanceof ILeafElementType) { // Empty token. Most probably a parser directive like indent/dedent in Python
final IElementType type = myLexTypes[curToken];
final TreeElement leaf = createLeaf(type, start, end);
curNode.rawAddChildrenWithoutNotifications(leaf);
}
curToken++;
}
return curToken;
}
private int collapseLeaves(final CompositeElement ast, final StartMarker startMarker) {
final int start = myLexStarts[startMarker.myLexemeIndex];
final int end = myLexStarts[startMarker.myDoneMarker.myLexemeIndex];
final TreeElement leaf = createLeaf(startMarker.myType, start, end);
ast.rawAddChildrenWithoutNotifications(leaf);
return startMarker.myDoneMarker.myLexemeIndex;
}
private static CompositeElement createComposite(final StartMarker marker) {
final IElementType type = marker.myType;
if (type == TokenType.ERROR_ELEMENT) {
String message = marker.myDoneMarker instanceof DoneWithErrorMarker ? ((DoneWithErrorMarker)marker.myDoneMarker).myMessage : null;
return Factory.createErrorElement(message);
}
if (type == null) {
throw new RuntimeException(UNBALANCED_MESSAGE);
}
return ASTFactory.composite(type);
}
@Nullable
public static String getErrorMessage(final LighterASTNode node) {
if (node instanceof ErrorItem) return ((ErrorItem)node).myMessage;
if (node instanceof StartMarker) {
final StartMarker marker = (StartMarker)node;
if (marker.myType == TokenType.ERROR_ELEMENT && marker.myDoneMarker instanceof DoneWithErrorMarker) {
return ((DoneWithErrorMarker)marker.myDoneMarker).myMessage;
}
}
return null;
}
private static class MyComparator implements ShallowNodeComparator<ASTNode, LighterASTNode> {
private final TripleFunction<ASTNode, LighterASTNode, FlyweightCapableTreeStructure<LighterASTNode>, ThreeState> custom;
private final MyTreeStructure myTreeStructure;
private MyComparator(TripleFunction<ASTNode, LighterASTNode, FlyweightCapableTreeStructure<LighterASTNode>, ThreeState> custom,
MyTreeStructure treeStructure) {
this.custom = custom;
myTreeStructure = treeStructure;
}
@Override
public ThreeState deepEqual(final ASTNode oldNode, final LighterASTNode newNode) {
ProgressIndicatorProvider.checkCanceled();
boolean oldIsErrorElement = oldNode instanceof PsiErrorElement;
boolean newIsErrorElement = newNode.getTokenType() == TokenType.ERROR_ELEMENT;
if (oldIsErrorElement != newIsErrorElement) return ThreeState.NO;
if (oldIsErrorElement) {
final PsiErrorElement e1 = (PsiErrorElement)oldNode;
return Comparing.equal(e1.getErrorDescription(), getErrorMessage(newNode)) ? ThreeState.UNSURE : ThreeState.NO;
}
if (custom != null) {
ThreeState customResult = custom.fun(oldNode, newNode, myTreeStructure);
if (customResult != ThreeState.UNSURE) {
return customResult;
}
}
if (newNode instanceof Token) {
final IElementType type = newNode.getTokenType();
final Token token = (Token)newNode;
if (oldNode instanceof ForeignLeafPsiElement) {
return type instanceof ForeignLeafType && ((ForeignLeafType)type).getValue().equals(oldNode.getText())
? ThreeState.YES
: ThreeState.NO;
}
if (oldNode instanceof LeafElement) {
if (type instanceof ForeignLeafType) return ThreeState.NO;
return ((LeafElement)oldNode).textMatches(token.getText())
? ThreeState.YES
: ThreeState.NO;
}
if (type instanceof ILightLazyParseableElementType) {
return ((TreeElement)oldNode).textMatches(token.getText())
? ThreeState.YES
: TreeUtil.isCollapsedChameleon(oldNode)
? ThreeState.NO // do not dive into collapsed nodes
: ThreeState.UNSURE;
}
if (oldNode.getElementType() instanceof ILazyParseableElementType && type instanceof ILazyParseableElementType ||
oldNode.getElementType() instanceof CustomParsingType && type instanceof CustomParsingType) {
return ((TreeElement)oldNode).textMatches(token.getText())
? ThreeState.YES
: ThreeState.NO;
}
}
return ThreeState.UNSURE;
}
@Override
public boolean typesEqual(final ASTNode n1, final LighterASTNode n2) {
if (n1 instanceof PsiWhiteSpaceImpl) {
return ourAnyLanguageWhitespaceTokens.contains(n2.getTokenType()) ||
n2 instanceof Token && ((Token)n2).myBuilder.myWhitespaces.contains(n2.getTokenType());
}
IElementType n1t;
IElementType n2t;
if (n1 instanceof ForeignLeafPsiElement) {
n1t = ((ForeignLeafPsiElement)n1).getForeignType();
n2t = n2.getTokenType();
}
else {
n1t = dereferenceToken(n1.getElementType());
n2t = dereferenceToken(n2.getTokenType());
}
return Comparing.equal(n1t, n2t);
}
public static IElementType dereferenceToken(IElementType probablyWrapper) {
if (probablyWrapper instanceof TokenWrapper) {
return dereferenceToken(((TokenWrapper)probablyWrapper).getDelegate());
}
return probablyWrapper;
}
@Override
public boolean hashCodesEqual(final ASTNode n1, final LighterASTNode n2) {
if (n1 instanceof LeafElement && n2 instanceof Token) {
boolean isForeign1 = n1 instanceof ForeignLeafPsiElement;
boolean isForeign2 = n2.getTokenType() instanceof ForeignLeafType;
if (isForeign1 != isForeign2) return false;
if (isForeign1) {
return n1.getText().equals(((ForeignLeafType)n2.getTokenType()).getValue());
}
return ((LeafElement)n1).textMatches(((Token)n2).getText());
}
if (n1 instanceof PsiErrorElement && n2.getTokenType() == TokenType.ERROR_ELEMENT) {
final PsiErrorElement e1 = (PsiErrorElement)n1;
if (!Comparing.equal(e1.getErrorDescription(), getErrorMessage(n2))) return false;
}
return ((TreeElement)n1).hc() == ((Node)n2).hc();
}
}
private static class MyTreeStructure implements FlyweightCapableTreeStructure<LighterASTNode> {
private final LimitedPool<Token> myPool;
private final LimitedPool<LazyParseableToken> myLazyPool;
private final StartMarker myRoot;
public MyTreeStructure(@NotNull StartMarker root, @Nullable final MyTreeStructure parentTree) {
if (parentTree == null) {
myPool = new LimitedPool<Token>(1000, new LimitedPool.ObjectFactory<Token>() {
@Override
public void cleanup(final Token token) {
token.clean();
}
@Override
public Token create() {
return new TokenNode();
}
});
myLazyPool = new LimitedPool<LazyParseableToken>(200, new LimitedPool.ObjectFactory<LazyParseableToken>() {
@Override
public void cleanup(final LazyParseableToken token) {
token.clean();
}
@Override
public LazyParseableToken create() {
return new LazyParseableToken();
}
});
}
else {
myPool = parentTree.myPool;
myLazyPool = parentTree.myLazyPool;
}
myRoot = root;
}
@Override
@NotNull
public LighterASTNode getRoot() {
return myRoot;
}
@Override
public LighterASTNode getParent(@NotNull final LighterASTNode node) {
if (node instanceof StartMarker) {
return ((StartMarker)node).myParent;
}
throw new UnsupportedOperationException("Unknown node type: " + node);
}
@Override
@NotNull
public LighterASTNode prepareForGetChildren(@NotNull final LighterASTNode node) {
return node;
}
private int count;
private LighterASTNode[] nodes;
@Override
public int getChildren(@NotNull final LighterASTNode item, @NotNull final Ref<LighterASTNode[]> into) {
if (item instanceof LazyParseableToken) {
final FlyweightCapableTreeStructure<LighterASTNode> tree = ((LazyParseableToken)item).parseContents();
final LighterASTNode root = tree.getRoot();
return tree.getChildren(tree.prepareForGetChildren(root), into); // todo: set offset shift for kids?
}
if (item instanceof Token || item instanceof ErrorItem) return 0;
StartMarker marker = (StartMarker)item;
count = 0;
ProductionMarker child = marker.myFirstChild;
int lexIndex = marker.myLexemeIndex;
while (child != null) {
lexIndex = insertLeaves(lexIndex, child.myLexemeIndex, marker.myBuilder);
if (child instanceof StartMarker && ((StartMarker)child).myDoneMarker.myCollapse) {
int lastIndex = ((StartMarker)child).myDoneMarker.myLexemeIndex;
insertLeaf(child.getTokenType(), marker.myBuilder, child.myLexemeIndex, lastIndex);
}
else {
ensureCapacity();
nodes[count++] = child;
}
if (child instanceof StartMarker) {
lexIndex = ((StartMarker)child).myDoneMarker.myLexemeIndex;
}
child = child.myNext;
}
insertLeaves(lexIndex, marker.myDoneMarker.myLexemeIndex, marker.myBuilder);
into.set(nodes);
nodes = null;
return count;
}
@Override
public void disposeChildren(final LighterASTNode[] nodes, final int count) {
if (nodes == null) return;
for (int i = 0; i < count; i++) {
final LighterASTNode node = nodes[i];
if (node instanceof LazyParseableToken) {
myLazyPool.recycle((LazyParseableToken)node);
}
else if (node instanceof Token) {
myPool.recycle((Token)node);
}
}
}
private void ensureCapacity() {
LighterASTNode[] old = nodes;
if (old == null) {
old = new LighterASTNode[10];
nodes = old;
}
else if (count >= old.length) {
LighterASTNode[] newStore = new LighterASTNode[count * 3 / 2];
System.arraycopy(old, 0, newStore, 0, count);
nodes = newStore;
}
}
private int insertLeaves(int curToken, int lastIdx, PsiBuilderImpl builder) {
lastIdx = Math.min(lastIdx, builder.myLexemeCount);
while (curToken < lastIdx) {
insertLeaf(builder.myLexTypes[curToken], builder, curToken, curToken + 1);
curToken++;
}
return curToken;
}
private void insertLeaf(IElementType type, PsiBuilderImpl builder, int startLexemeIndex, int endLexemeIndex) {
final int start = builder.myLexStarts[startLexemeIndex];
final int end = builder.myLexStarts[endLexemeIndex];
if (start > end || ((start == end) && !(type instanceof ILeafElementType))) return;
final Token lexeme;
if (type instanceof ILightLazyParseableElementType) {
lexeme = myLazyPool.alloc();
LazyParseableToken lazyParseableToken = (LazyParseableToken)lexeme;
lazyParseableToken.myParent = this;
lazyParseableToken.myStartIndex = startLexemeIndex;
lazyParseableToken.myEndIndex = endLexemeIndex;
}
else {
lexeme = myPool.alloc();
}
lexeme.myBuilder = builder;
lexeme.myTokenType = type;
lexeme.myTokenStart = start;
lexeme.myTokenEnd = end;
ensureCapacity();
nodes[count++] = lexeme;
}
}
private static class ASTConverter implements Convertor<Node, ASTNode> {
private final StartMarker myRoot;
public ASTConverter(final StartMarker root) {
myRoot = root;
}
@Override
public ASTNode convert(final Node n) {
if (n instanceof Token) {
final Token token = (Token)n;
return token.myBuilder.createLeaf(token.getTokenType(), token.myTokenStart, token.myTokenEnd);
}
else if (n instanceof ErrorItem) {
return Factory.createErrorElement(((ErrorItem)n).myMessage);
}
else {
final StartMarker startMarker = (StartMarker)n;
final CompositeElement composite = n == myRoot ? (CompositeElement)myRoot.myBuilder.createRootAST(myRoot)
: createComposite(startMarker);
startMarker.myBuilder.bind(startMarker, composite);
return composite;
}
}
}
@Override
public void setDebugMode(boolean dbgMode) {
myDebugMode = dbgMode;
}
public Lexer getLexer() {
return myLexer;
}
@NotNull
private TreeElement createLeaf(final IElementType type, final int start, final int end) {
CharSequence text = myCharTable.intern(myText, start, end);
if (myWhitespaces.contains(type)) {
return new PsiWhiteSpaceImpl(text);
}
if (type instanceof CustomParsingType) {
return (TreeElement)((CustomParsingType)type).parse(text, myCharTable);
}
if (type instanceof ILazyParseableElementType) {
return ASTFactory.lazy((ILazyParseableElementType)type, text);
}
return ASTFactory.leaf(type, text);
}
/**
* just to make removeRange method available.
*/
private static class MyList extends ArrayList<ProductionMarker> {
private static final Field ourElementDataField = ReflectionUtil.getDeclaredField(ArrayList.class, "elementData");
private Object[] cachedElementData;
@Override
public void removeRange(final int fromIndex, final int toIndex) {
super.removeRange(fromIndex, toIndex);
}
MyList() {
super(256);
}
@Override
public int lastIndexOf(final Object o) {
if (cachedElementData == null) return super.lastIndexOf(o);
for (int i = size() - 1; i >= 0; i--) {
if (cachedElementData[i] == o) return i;
}
return -1;
}
@Override
public void ensureCapacity(final int minCapacity) {
if (cachedElementData == null || minCapacity >= cachedElementData.length) {
super.ensureCapacity(minCapacity);
initCachedField();
}
}
private void initCachedField() {
if (ourElementDataField == null) return;
try {
cachedElementData = (Object[])ourElementDataField.get(this);
}
catch (Exception e) {
LOG.error(e);
}
}
}
@SuppressWarnings("unchecked")
@Override
public <T> T getUserDataUnprotected(@NotNull final Key<T> key) {
if (key == FileContextUtil.CONTAINING_FILE_KEY) return (T)myFile;
return myUserData != null ? (T)myUserData.get(key) : null;
}
@Override
public <T> void putUserDataUnprotected(@NotNull final Key<T> key, @Nullable final T value) {
if (key == FileContextUtil.CONTAINING_FILE_KEY) {
myFile = (PsiFile)value;
return;
}
if (myUserData == null) myUserData = ContainerUtil.newHashMap();
myUserData.put(key, value);
}
}