private static boolean isPossibleEmptyAttrs(PsiElement attrs) {
   if (!(attrs instanceof BnfParenExpression)) return false;
   if (attrs.getFirstChild().getNode().getElementType() != BnfTypes.BNF_LEFT_BRACE) return false;
   if (!(((BnfParenExpression) attrs).getExpression() instanceof BnfReferenceOrToken))
     return false;
   return isLastInRuleOrFree(attrs);
 }
  public String calcGenerics(@NotNull PsiElement context, InsertionContext insertionContext) {
    if (insertionContext.getCompletionChar() == '<') {
      return "";
    }

    assert context.isValid();
    if (myDiamond) {
      return "<>";
    }

    if (getObject() instanceof PsiClass) {
      PsiClass psiClass = (PsiClass) getObject();
      PsiResolveHelper resolveHelper =
          JavaPsiFacade.getInstance(psiClass.getProject()).getResolveHelper();
      PsiSubstitutor substitutor = getSubstitutor();
      StringBuilder builder = new StringBuilder();
      for (PsiTypeParameter parameter : psiClass.getTypeParameters()) {
        PsiType substitute = substitutor.substitute(parameter);
        if (substitute == null
            || (PsiUtil.resolveClassInType(substitute) == parameter
                && resolveHelper.resolveReferencedClass(parameter.getName(), context)
                    != CompletionUtil.getOriginalOrSelf(parameter))) {
          return "";
        }
        if (builder.length() > 0) {
          builder.append(", ");
        }
        builder.append(substitute.getCanonicalText());
      }
      if (builder.length() > 0) {
        return "<" + builder + ">";
      }
    }
    return "";
  }
  private static Collection<String> suggestKeywords(PsiElement position) {
    TextRange posRange = position.getTextRange();
    BnfFile posFile = (BnfFile) position.getContainingFile();
    BnfRule statement = PsiTreeUtil.getTopmostParentOfType(position, BnfRule.class);
    final TextRange range;
    if (statement != null) {
      range = new TextRange(statement.getTextRange().getStartOffset(), posRange.getStartOffset());
    } else {
      int offset = posRange.getStartOffset();
      for (PsiElement cur = GrammarUtil.getDummyAwarePrevSibling(position);
          cur != null;
          cur = GrammarUtil.getDummyAwarePrevSibling(cur)) {
        if (cur instanceof BnfAttrs) offset = cur.getTextRange().getEndOffset();
        else if (cur instanceof BnfRule) offset = cur.getTextRange().getStartOffset();
        else continue;
        break;
      }
      range = new TextRange(offset, posRange.getStartOffset());
    }
    final String text =
        range.isEmpty()
            ? CompletionInitializationContext.DUMMY_IDENTIFIER
            : range.substring(posFile.getText());

    PsiFile file =
        PsiFileFactory.getInstance(posFile.getProject())
            .createFileFromText("a.bnf", BnfLanguage.INSTANCE, text, true, false);
    int completionOffset = posRange.getStartOffset() - range.getStartOffset();
    GeneratedParserUtilBase.CompletionState state =
        new GeneratedParserUtilBase.CompletionState(completionOffset) {
          @Override
          public String convertItem(Object o) {
            // we do not have other keywords
            return o instanceof String ? (String) o : null;
          }
        };
    file.putUserData(GeneratedParserUtilBase.COMPLETION_STATE_KEY, state);
    TreeUtil.ensureParsed(file.getNode());
    return state.items;
  }
 private static boolean isLastInRuleOrFree(PsiElement element) {
   PsiElement parent =
       PsiTreeUtil.getParentOfType(
           element, BnfRule.class, GeneratedParserUtilBase.DummyBlock.class);
   if (parent instanceof GeneratedParserUtilBase.DummyBlock) return true;
   if (!(parent instanceof BnfRule)) return false;
   for (PsiElement cur = element, next = cur.getNextSibling();
       next == null || next instanceof PsiComment || next instanceof PsiWhiteSpace;
       cur = next, next = cur.getNextSibling()) {
     if (next == null) {
       PsiElement curParent = cur.getParent();
       while (next == null && curParent != parent) {
         next = curParent.getNextSibling();
         curParent = curParent.getParent();
       }
       if (curParent == parent) return true;
       next = PsiTreeUtil.getDeepestFirst(next);
     }
   }
   return false;
 }