@Override
  public final @NotNull ASTNode parse(final IElementType root, final PsiBuilder builder) {
    final PsiBuilder.Marker fileMarker = builder.mark();
    final ArrayList<PsiBuilder.Marker> containerMarkers = new ArrayList<>();
    if (myContainerTypes != null) {
      for (IElementType ignored : myContainerTypes) {
        final PsiBuilder.Marker containerMarker = builder.mark();
        containerMarkers.add(containerMarker);
      }
    }
    final PsiBuilder.Marker documentMarker = builder.mark();
    while (builder.getTokenType() == myEolTokenType) {
      advanceLexer(builder);
    }

    int currentIndent = 0;
    boolean eolSeen = false;

    if (builder.getTokenType() == myIndentTokenType) {
      currentIndent = builder.getTokenText().length();
      advanceLexer(builder);
    }

    final Stack<BlockInfo> stack = new Stack<>();
    stack.push(new BlockInfo(currentIndent, builder.mark(), builder.getTokenType()));

    PsiBuilder.Marker startLineMarker = null;

    while (!builder.eof()) {
      final IElementType type = builder.getTokenType();
      // EOL
      if (type == myEolTokenType) {
        // Handle variant with several EOLs
        if (startLineMarker == null) {
          startLineMarker = builder.mark();
        }
        eolSeen = true;
        currentIndent = 0;
      }
      else {
        if (type == myIndentTokenType) {
          //noinspection ConstantConditions
          currentIndent = builder.getTokenText().length();
        }
        else {
          if (!eolSeen && !stack.isEmpty() && currentIndent > 0 && currentIndent < stack.peek().getIndent()) {
            // sometimes we do not have EOL between indents
            eolSeen = true;
          }
          if (isCustomTagDelimiter(type)) {
            builder.advanceLexer();
            stack.push(new BlockInfo(currentIndent, builder.mark(), type));
          }
          if (eolSeen) {
            if (startLineMarker != null) {
              startLineMarker.rollbackTo();
              startLineMarker = null;
            }
            // Close indentation blocks
            while (!stack.isEmpty() && currentIndent < stack.peek().getIndent()) {
              final BlockInfo blockInfo = stack.pop();
              closeBlock(builder, blockInfo.getMarker(), blockInfo.getStartTokenType());
            }

            if (!stack.isEmpty()) {
              final BlockInfo blockInfo = stack.peek();
              if (currentIndent >= blockInfo.getIndent()) {
                if (currentIndent == blockInfo.getIndent()) {
                  final BlockInfo info = stack.pop();
                  closeBlock(builder, info.getMarker(), info.getStartTokenType());
                }
                passEOLsAndIndents(builder);
                stack.push(new BlockInfo(currentIndent, builder.mark(), type));
              }
            }
            eolSeen = false;
            currentIndent = 0;
          }
        }
      }
      advanceLexer(builder);
    }

    // Close all left opened markers
    if (startLineMarker != null){
      startLineMarker.drop();
    }
    while (!stack.isEmpty()){
      final BlockInfo blockInfo = stack.pop();
      closeBlock(builder, blockInfo.getMarker(), blockInfo.getStartTokenType());
    }

    documentMarker.done(myDocumentType);

    if (myContainerTypes != null) {
      for (int i = containerMarkers.size() - 1; i >= 0; i--) {
        final PsiBuilder.Marker marker = containerMarkers.get(i);
        marker.done(myContainerTypes.get(i));
      }
    }

    fileMarker.done(root);
    return builder.getTreeBuilt();
  }
@Override
  public final @NotNull ASTNode parse(final IElementType root, final PsiBuilder builder)