private @NotNull StartMarker prepareLightTree() {
    if (myProduction.isEmpty()) {
      LOG.error("Parser produced no markers. Text:\n" + myText);
    }
    // build tree only once to avoid threading issues in read-only PSI
    StartMarker rootMarker = (StartMarker)Objects.requireNonNull(myProduction.getStartMarkerAt(0));
    if (rootMarker.myFirstChild != null) return rootMarker;

    myTokenTypeChecked = true;
    balanceWhiteSpaces();

    rootMarker.myParent = rootMarker.myFirstChild = rootMarker.myLastChild = rootMarker.myNext = null;
    StartMarker curNode = rootMarker;
    ArrayDeque<StartMarker> nodes = new ArrayDeque<>();
    nodes.addLast(rootMarker);

    int lastErrorIndex = -1;
    int maxDepth = 0;
    int curDepth = 0;
    boolean hasCollapsedChameleons = false;
    int[] productions = myProduction.elements();
    for (int i = 1, size = myProduction.size(); i < size; i++) {
      int id = productions[i];
      ProductionMarker item = id > 0 ? pool.get(id) : null;

      if (item instanceof StartMarker) {
        StartMarker marker = (StartMarker)item;
        marker.myParent = curNode;
        marker.myFirstChild = marker.myLastChild = marker.myNext = null;
        curNode.addChild(marker);
        nodes.addLast(curNode);
        curNode = marker;
        curDepth++;
        if (curDepth > maxDepth) maxDepth = curDepth;
      }
      else if (item instanceof ErrorItem) {
        item.myParent = curNode;
        int curToken = item.myLexemeIndex;
        if (curToken == lastErrorIndex) continue;
        lastErrorIndex = curToken;
        curNode.addChild(item);
      }
      else {
        if (isCollapsedChameleon(curNode)) {
          hasCollapsedChameleons = true;
        }
        assertMarkersBalanced(id < 0 && pool.get(-id) == curNode, item);
        curNode = nodes.removeLast();
        curDepth--;
      }
    }

    if (myCurrentLexeme < myLexemeCount) {
      List<IElementType> missed = ContainerUtil.subArrayAsList(myLexTypes, myCurrentLexeme, myLexemeCount);
      LOG.error("Tokens " + missed + " were not inserted into the tree. "
                + (myFile == null
                   ? ""
                   : myFile.getLanguage()),
                new Attachment("missedTokensFragment.txt", myText.toString()));
    }

    if (rootMarker.getEndIndex() < myLexemeCount) {
      List<IElementType> missed = ContainerUtil.subArrayAsList(myLexTypes, rootMarker.getEndIndex(), myLexemeCount);
      LOG.error("Tokens " + missed + " are outside of root element \"" + rootMarker.myType + "\".",
                new Attachment("outsideTokensFragment.txt", myText.toString()));
    }

    assertMarkersBalanced(curNode == rootMarker, curNode);

    checkTreeDepth(maxDepth, rootMarker.getTokenType() instanceof IFileElementType, hasCollapsedChameleons);

    clearCachedTokenType();
    return rootMarker;
  }
private @NotNull StartMarker prepareLightTree()