private function computeRuleReducible()

in src/parser/PhutilParserGenerator.php [420:522]


  private function computeRuleReducible($rule, array &$reducible) {
    $epsilon = $this->getEpsilonSymbol();
    $end = $this->getEndSymbol();

    $productions = $this->rules[$rule];

    // In the first pass, try to find a trivially reducible production, e.g. one
    // with epsilon or only terminals. Also, remove recursive productions (those
    // which directly involve the rule itself) because we know we won't be able
    // to reduce them. If we're lucky, this will allow us to determine that the
    // rule is reducible without recursion. For example, we can immediately
    // reduce these productions:
    //
    //    R -> a
    //    R -> b c d
    //    R -> (epsilon)
    //
    // We can never reduce these productions:
    //
    //    R -> R
    //    R -> a R b
    //
    // We might be able to reduce these productions, but they aren't as cheap
    // or easy to figure out, since we need to first determine if other rules
    // can be reduced:
    //
    //    R -> X Y
    //    R -> X a
    //
    // If we find a reduction, we return immediately.

    foreach ($productions as $key => $production) {
      $has_only_terminals = true;
      foreach ($production as $symbol) {
        if ($symbol == $end) {
          break;
        } else if ($symbol == $epsilon) {
          // The rule contains an epsilon production, which can always reduce
          // it.
          return true;
        } else if ($symbol == $rule) {
          // The rule contains itself; this production is never reducible. We
          // must find another reducible production.
          unset($productions[$key]);
          continue 2;
        } else if ($this->isTerminal($symbol)) {
          // This is a terminal; keep looking. We'll be able to reduce the
          // production if it contains only terminals.
          continue;
        } else {
          // This is a rule, so we can't trivially reduce it. We'll keep it
          // for the next round if we can't find any trivial reductions.
          $has_only_terminals = false;
          break;
        }
      }

      if ($has_only_terminals) {
        return true;
      }
    }

    // If we have no productions left, this rule can't be reduced.
    if (empty($productions)) {
      return false;
    }

    // We have remaining productions which include other rules. Look for a
    // nontrivial reduction. For example:
    //
    //    R -> X Y
    //    X -> x
    //    Y -> y
    //
    // In this case, X and Y are both reducible, so "X Y" is reducible and thus
    // R is reducible.
    foreach ($productions as $production) {
      $can_reduce = true;
      foreach ($production as $symbol) {
        // NOTE: We don't need to check for epsilon here, because we would
        // already have determined the rule was reducible if we had an epsilon
        // production.
        if ($symbol == $end) {
          break;
        } else if ($this->isTerminal($symbol)) {
          continue;
        } else if (!$this->isRuleReducible($symbol, $reducible)) {
          $can_reduce = false;
          break;
        }
      }

      if ($can_reduce) {
        // The production contained only terminals and reducible rules, so it
        // is reducible. We're good and don't need to examine remaining
        // productions.
        return true;
      }
    }

    // We didn't find any reducible productions.
    return false;
  }