Package org.intellij.grammar.generator

Source Code of org.intellij.grammar.generator.ExpressionHelper$OperatorInfo

/*
* Copyright 2011-2014 Gregory Shrago
*
* 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 org.intellij.grammar.generator;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.Function;
import com.intellij.util.ObjectUtils;
import com.intellij.util.PairConsumer;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashSet;
import org.intellij.grammar.analysis.BnfFirstNextAnalyzer;
import org.intellij.grammar.psi.*;
import org.intellij.grammar.psi.impl.BnfElementFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

import static org.intellij.grammar.generator.ParserGeneratorUtil.Rule;
import static org.intellij.grammar.generator.ParserGeneratorUtil.getChildExpressions;
import static org.intellij.grammar.generator.RuleGraphHelper.Cardinality.OPTIONAL;

/**
* @author gregsh
*/
public class ExpressionHelper {
    public static final Function<BnfExpression, String> TO_STRING = new Function<BnfExpression, String>() {
    @Override
    public String fun(BnfExpression expression) {
      return expression.getText();
    }
  };
  private final BnfFile myFile;
  private final RuleGraphHelper myRuleGraph;
  private final boolean myAddWarnings;

  private final Map<BnfRule, ExpressionInfo> myExpressionMap = ContainerUtil.newTroveMap();
  private final Map<BnfRule, BnfRule> myRootRulesMap = ContainerUtil.newTroveMap();

  private static final Key<CachedValue<ExpressionHelper>> EXPRESSION_HELPER_KEY = Key.create("EXPRESSION_HELPER_KEY");
  public static ExpressionHelper getCached(final BnfFile file) {
    CachedValue<ExpressionHelper> value = file.getUserData(EXPRESSION_HELPER_KEY);
    if (value == null) {
      file.putUserData(EXPRESSION_HELPER_KEY, value = CachedValuesManager.getManager(file.getProject()).createCachedValue(new CachedValueProvider<ExpressionHelper>() {
          @Nullable
          @Override
          public Result<ExpressionHelper> compute() {
            return new Result<ExpressionHelper>(new ExpressionHelper(file, RuleGraphHelper.getCached(file), false), file);
          }
        }, false));
    }
    return value.getValue();
  }


  public ExpressionHelper(BnfFile file, RuleGraphHelper ruleGraph, boolean addWarnings) {
    myFile = file;
    myRuleGraph = ruleGraph;
    myAddWarnings = addWarnings;
    buildExpressionRules();
  }

  public boolean hasExpressions() {
    return !myExpressionMap.isEmpty();
  }

  public void addWarning(String text) {
    if (!myAddWarnings) return;
    ParserGeneratorUtil.addWarning(myFile.getProject(), text);
  }

  public ExpressionInfo getExpressionInfo(BnfRule rule) {
    BnfRule root = myRootRulesMap.get(rule);
    ExpressionInfo info = root == null ? null : myExpressionMap.get(root);
    if (info == null) return null;
    if (info.rootRule == rule || Rule.isPrivate(rule)) return info;
    return info.priorityMap.containsKey(rule) ? info : null;
  }

  private void buildExpressionRules() {
    BnfFirstNextAnalyzer analyzer = new BnfFirstNextAnalyzer();
    for (BnfRule rule : myFile.getRules()) {
      if (Rule.isPrivate(rule) || Rule.isFake(rule)) continue;
      if (myRootRulesMap.containsKey(rule)) continue;
      Map<PsiElement, RuleGraphHelper.Cardinality> contentRules = myRuleGraph.getFor(rule);
      if (!contentRules.isEmpty()) continue;
      if (!analyzer.asStrings(analyzer.calcFirst(rule)).contains(rule.getName())) continue;

      ExpressionInfo expressionInfo = new ExpressionInfo(rule);
      addToPriorityMap(rule, myRuleGraph.getExtendsRules(rule), expressionInfo);
      List<BnfRule> rules = ParserGeneratorUtil.topoSort(expressionInfo.priorityMap.keySet(), myRuleGraph);
      for (BnfRule r : rules) {
        buildOperatorMap(r, rule, expressionInfo);
      }
      if (!expressionInfo.priorityMap.isEmpty()) {
        myRootRulesMap.put(rule, rule);
        myExpressionMap.put(rule, expressionInfo);
      }
    }
  }

  private void addToPriorityMap(BnfRule rule, Collection<BnfRule> rulesCluster, ExpressionInfo info) {
    Collection<BnfRule> subRules = myRuleGraph.getSubRules(rule);
    int priority = rulesCluster.contains(rule) ? -1 : info.nextPriority++;

    for (BnfRule subRule : subRules) {
      if (info.priorityMap.containsKey(subRule)) {
        addWarning(subRule + " has duplicate appearance!");
        continue;
      }
      BnfRule prev = myRootRulesMap.put(subRule, info.rootRule);
      if (prev != null) {
        addWarning(
            subRule + " must not be in several expression hierarchies: " + prev.getName() + " and " + info.rootRule.getName());
      }

      if (rulesCluster.contains(subRule)) {
        if (!Rule.isPrivate(subRule) || !myRuleGraph.getFor(subRule).isEmpty()) {
          info.priorityMap.put(subRule, priority == -1 ? info.nextPriority++ : priority);
        }
      }
      else if (ParserGeneratorUtil.Rule.isPrivate(subRule)) {
        addToPriorityMap(subRule, rulesCluster, info);
        info.privateGroups.add(subRule);
      }
      else {
        addWarning(subRule + ": priority group must be 'private'");
      }
    }
  }

  private void buildOperatorMap(BnfRule rule, BnfRule rootRule, ExpressionInfo expressionInfo) {
    Map<PsiElement, RuleGraphHelper.Cardinality> ruleContent = myRuleGraph.getFor(rule);
    RuleGraphHelper.Cardinality cardinality = ruleContent.get(rootRule);
    BnfRule rootRuleSubst = rootRule;
    if (cardinality == null) {
      Collection<BnfRule> extendsRules = myRuleGraph.getExtendsRules(rootRule);
      for (PsiElement r : ruleContent.keySet()) {
        if (r instanceof BnfRule && extendsRules.contains(r)) {
          cardinality = ruleContent.get(r);
          rootRuleSubst = (BnfRule)r;
          break;
        }
      }
    }
    String rootRuleName = rootRule.getName();
    List<BnfExpression> childExpressions = getChildExpressions(rule.getExpression());
    OperatorInfo info;
    if (cardinality == null) {
      // atom
      info = new OperatorInfo(rule, OperatorType.ATOM, rule.getExpression(), null);
    }
    else if (childExpressions.size() < 2) {
      addWarning("invalid expression definition for " + rule + ": 2 or more arguments expected");
      info = new OperatorInfo(rule, OperatorType.ATOM, rule.getExpression(), null);
    }
    else if (cardinality == RuleGraphHelper.Cardinality.REQUIRED) {
      // postfix or prefix unary expression
      int index = indexOf(rootRuleSubst, 0, childExpressions, expressionInfo);
      BnfRule arg1 = substRule(childExpressions, index, rootRule);
      if (index == 0) {
        info = new OperatorInfo(rule, OperatorType.POSTFIX, combine(childExpressions.subList(1, childExpressions.size())), null, arg1, null);
      }
      else if (index == -1) {
        addWarning(rule +": " + rootRuleName + " reference not found, treating as ATOM");
        info = new OperatorInfo(rule, OperatorType.ATOM, rule.getExpression(), null);
      }
      else {
        info = new OperatorInfo(rule, OperatorType.PREFIX, combine(childExpressions.subList(0, index)),
                                combine(childExpressions.subList(index + 1, childExpressions.size())), arg1, null);
      }
    }
    else if (cardinality == RuleGraphHelper.Cardinality.AT_LEAST_ONE) {
      // binary or n-ary expression
      int index1 = indexOf(rootRuleSubst, 0, childExpressions, expressionInfo);
      int index2 = indexOf(rootRuleSubst, 1, childExpressions, expressionInfo);
      if (index1 != 0) {
        addWarning(rule +": binary or n-ary expression cannot have prefix, treating as ATOM");
        info = new OperatorInfo(rule, OperatorType.ATOM, rule.getExpression(), null);
      }
      else if (index2 == 1) {
        addWarning(rule + ": binary expression needs operator, treating as ATOM");
        info = new OperatorInfo(rule, OperatorType.ATOM, rule.getExpression(), null);
      }
      else {
        BnfRule arg1 = substRule(childExpressions, index1, rootRule);
        if (index2 == -1) {
          BnfExpression lastExpression = childExpressions.get(1);
          boolean badNAry = childExpressions.size() != 2 || !(lastExpression instanceof BnfQuantified) ||
                            !(((BnfQuantified)lastExpression).getQuantifier().getText().equals("+")) ||
                            !(((BnfQuantified)lastExpression).getExpression() instanceof BnfParenExpression);
          List<BnfExpression> childExpressions2 = badNAry ? Collections.<BnfExpression>emptyList() :
                                                  getChildExpressions(
                                                    ((BnfParenExpression)((BnfQuantified)lastExpression).getExpression()).getExpression());
          int index3 = indexOf(rootRuleSubst, 0, childExpressions2, expressionInfo);
          if (badNAry || index3 == -1) {
            addWarning(
                rule + ": '" + rootRuleName + " ( <op> " + rootRuleName + ") +' expected for N-ary operator, treating as POSTFIX"
            );
            info = new OperatorInfo(rule, OperatorType.POSTFIX, combine(childExpressions.subList(1, childExpressions.size())), null,
                                    arg1, null);
          }
          else {
            BnfRule arg2 = substRule(childExpressions2, index3, rootRule);
            info = new OperatorInfo(rule, OperatorType.N_ARY, combine(childExpressions2.subList(0, index3)),
                                    combine(childExpressions2.subList(index3 + 1, childExpressions2.size())), arg1, arg2);
          }
        }
        else {
          BnfRule arg2 = substRule(childExpressions, index2, rootRule);
          info = new OperatorInfo(rule, OperatorType.BINARY, combine(childExpressions.subList(index1 + 1, index2)),
                                  combine(childExpressions.subList(index2 + 1, childExpressions.size())), arg1, arg2);
        }
      }
    }
    else {
      addWarning(rule +": unexpected cardinality " + cardinality + " of " + rootRuleName +", treating as ATOM");
      info = new OperatorInfo(rule, OperatorType.ATOM, rule.getExpression(), null);
    }
    expressionInfo.operatorMap.put(rule, info);
  }

  @Nullable
  private BnfRule substRule(List<BnfExpression> list, int idx, BnfRule rootRule) {
    if (idx < 0) return null;
    BnfRule rule = myFile.getRule(list.get(idx).getText());
    return rule == rootRule? null : rule;
  }

  private static final Key<List<BnfExpression>> ORIGINAL_EXPRESSIONS = Key.create("ORIGINAL_EXPRESSIONS");
  private static BnfExpression combine(List<BnfExpression> list) {
    if (list.isEmpty()) return null;
    if (list.size() == 1) return list.get(0);
    Project project = list.get(0).getProject();
    String text = StringUtil.join(list, TO_STRING, " ");
    BnfExpression result = BnfElementFactory.createExpressionFromText(project, text);
    result.putUserData(ORIGINAL_EXPRESSIONS, list);
    return result;
  }

  @NotNull
  public static List<BnfExpression> getOriginalExpressions(BnfExpression expression) {
    List<BnfExpression> data = expression.getUserData(ORIGINAL_EXPRESSIONS);
    return data == null ? Collections.singletonList(expression) : data;
  }

  @NotNull
  public RuleGraphHelper.Cardinality fixCardinality(BnfRule rule, PsiElement tree, RuleGraphHelper.Cardinality type) {
    if (type.optional()) return type;
    // in Expression parsing mode REQUIRED may go OPTIONAL
    ExpressionHelper.ExpressionInfo info = getExpressionInfo(rule);
    ExpressionHelper.OperatorInfo operatorInfo = info == null ? null : info.operatorMap.get(rule);
    if (operatorInfo == null || operatorInfo.type == ExpressionHelper.OperatorType.ATOM) return type;

    // emulate expr-parsing pin processing
    if ((operatorInfo.type == OperatorType.BINARY ||
         operatorInfo.type == OperatorType.N_ARY ||
         operatorInfo.type == OperatorType.POSTFIX) &&
        ObjectUtils.chooseNotNull(operatorInfo.arg1, info.rootRule) == tree ||
        isRealAncestor(rule, operatorInfo.operator, tree)) {
      // pinned! return as is
      return type;
    }
    else {
      return type.and(OPTIONAL);
    }
  }

  private boolean isRealAncestor(BnfRule rule, BnfExpression expression, PsiElement target) {
    List<BnfExpression> list = getOriginalExpressions(expression);
    if (list.size() == 1 && PsiTreeUtil.isAncestor(list.get(0), target, false)) return true;
    for (BnfExpression expr : list) {
      Map<PsiElement, RuleGraphHelper.Cardinality> map =
        myRuleGraph.collectMembers(rule, expr, new THashSet<PsiElement>());
      if (map.containsKey(target)) return true;
    }
    return false;
  }

  private int indexOf(BnfRule rootRule,
                      int startIndex,
                      List<BnfExpression> childExpressions,
                      ExpressionInfo expressionInfo) {
    Collection<BnfRule> extendsRules = myRuleGraph.getExtendsRules(rootRule);
    for (int i = startIndex, childExpressionsSize = childExpressions.size(); i < childExpressionsSize; i++) {
      BnfRule rule = myFile.getRule(childExpressions.get(i).getText());
      if (rootRule == rule || extendsRules.contains(rule) || expressionInfo.privateGroups.contains(rule)) {
        return i;
      }
    }
    return -1;
  }

  public static class ExpressionInfo {
    public final BnfRule rootRule;
    public final Map<BnfRule, Integer> priorityMap = new LinkedHashMap<BnfRule, Integer>();
    public final Map<BnfRule, OperatorInfo> operatorMap = new LinkedHashMap<BnfRule, OperatorInfo>();
    public final Set<BnfRule> privateGroups = ContainerUtil.newHashSet();
    public int nextPriority;

    public ExpressionInfo(BnfRule rootRule) {
      this.rootRule = rootRule;
    }

    @Override
    public String toString() {
      StringBuilder sb = new StringBuilder("Expression root: " + rootRule.getName());
      sb.append("\nOperator priority table:\n");
      dumpPriorityTable(sb);
      return sb.toString();
    }

    public StringBuilder dumpPriorityTable(StringBuilder sb) {
      return dumpPriorityTable(sb, new PairConsumer<StringBuilder, OperatorInfo>() {
        @Override
        public void consume(StringBuilder sb, OperatorInfo operatorInfo) {
          sb.append(operatorInfo);
        }
      });
    }

    public StringBuilder dumpPriorityTable(StringBuilder sb, PairConsumer<StringBuilder, OperatorInfo> printer) {
      for (int i = 0; i < nextPriority; i++) {
        sb.append(i).append(":");
        for (BnfRule rule : priorityMap.keySet()) {
          if (priorityMap.get(rule) == i) {
            sb.append(" ");
            printer.consume(sb, operatorMap.get(rule));
          }
        }
        sb.append("\n");
      }
      return sb;
    }

    public int getPriority(BnfRule subRule) {
      if (subRule == rootRule) return 0;
      Integer integer = priorityMap.get(subRule);
      return integer == null ? -1 : integer;
    }
  }

  public static enum OperatorType {ATOM, PREFIX, POSTFIX, BINARY, N_ARY}

  public static class OperatorInfo {
    public final BnfRule rule;
    public final OperatorType type;
    public final BnfExpression operator;
    public final BnfExpression tail; // null for postfix
    public final BnfRule arg1;
    public final BnfRule arg2;

    public OperatorInfo(BnfRule rule, OperatorType type, BnfExpression operator, BnfExpression tail) {
      this(rule, type, operator, tail, null, null);
    }

    public OperatorInfo(BnfRule rule, OperatorType type, BnfExpression operator, BnfExpression tail,
                        @Nullable BnfRule arg1,
                        @Nullable BnfRule arg2) {
      if (operator == null) {
        throw new AssertionError(rule + ": operator must not be null");
      }
      this.rule = rule;
      this.type = type;
      this.operator = operator;
      this.tail = tail;
      this.arg1 = arg1;
      this.arg2 = arg2;
    }

    @Override
    public String toString() {
      return type + "(" + rule.getName() + ")";
    }
  }
}
TOP

Related Classes of org.intellij.grammar.generator.ExpressionHelper$OperatorInfo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.