1<?php 2 3declare(strict_types=1); 4 5namespace Antlr\Antlr4\Runtime\Error\Listeners; 6 7use Antlr\Antlr4\Runtime\Atn\ATNConfigSet; 8use Antlr\Antlr4\Runtime\Dfa\DFA; 9use Antlr\Antlr4\Runtime\Interval; 10use Antlr\Antlr4\Runtime\Parser; 11use Antlr\Antlr4\Runtime\Utils\BitSet; 12 13/** 14 * This implementation of {@see ANTLRErrorListener} can be used to identify 15 * certain potential correctness and performance problems in grammars. "Reports" 16 * are made by calling {@see Parser::notifyErrorListeners()} with the appropriate 17 * message. 18 * 19 * - Ambiguities: These are cases where more than one path through the 20 * grammar can match the input. 21 * 22 * - Weak context sensitivity: These are cases where full-context 23 * prediction resolved an SLL conflict to a unique alternative which equaled the 24 * minimum alternative of the SLL conflict. 25 * 26 * - Strong (forced) context sensitivity: These are cases where the 27 * full-context prediction resolved an SLL conflict to a unique alternative, 28 * and the minimum alternative of the SLL conflict was found to not be 29 * a truly viable alternative. Two-stage parsing cannot be used for inputs where 30 * this situation occurs. 31 * 32 * @author Sam Harwell 33 */ 34class DiagnosticErrorListener extends BaseErrorListener 35{ 36 /** 37 * When `true`, only exactly known ambiguities are reported. 38 * 39 * @var bool 40 */ 41 protected $exactOnly; 42 43 public function __construct(bool $exactOnly = true) 44 { 45 $this->exactOnly = $exactOnly; 46 } 47 48 public function reportAmbiguity( 49 Parser $recognizer, 50 DFA $dfa, 51 int $startIndex, 52 int $stopIndex, 53 bool $exact, 54 ?BitSet $ambigAlts, 55 ATNConfigSet $configs 56 ) : void { 57 if ($this->exactOnly && !$exact) { 58 return; 59 } 60 61 $tokenStream = $recognizer->getTokenStream(); 62 63 $msg = \sprintf( 64 'reportAmbiguity d=%s: ambigAlts=%s, input=\'%s\'', 65 $this->getDecisionDescription($recognizer, $dfa), 66 $this->getConflictingAlts($ambigAlts, $configs), 67 $tokenStream === null ? '' : $tokenStream->getTextByInterval(new Interval($startIndex, $stopIndex)) 68 ); 69 70 $recognizer->notifyErrorListeners($msg); 71 } 72 73 public function reportAttemptingFullContext( 74 Parser $recognizer, 75 DFA $dfa, 76 int $startIndex, 77 int $stopIndex, 78 ?BitSet $conflictingAlts, 79 ATNConfigSet $configs 80 ) : void { 81 $tokenStream = $recognizer->getTokenStream(); 82 83 $msg = \sprintf( 84 'reportAttemptingFullContext d=%s, input=\'%s\'', 85 $this->getDecisionDescription($recognizer, $dfa), 86 $tokenStream === null ? '' : $tokenStream->getTextByInterval(new Interval($startIndex, $stopIndex)) 87 ); 88 89 $recognizer->notifyErrorListeners($msg); 90 } 91 92 public function reportContextSensitivity( 93 Parser $recognizer, 94 DFA $dfa, 95 int $startIndex, 96 int $stopIndex, 97 int $prediction, 98 ATNConfigSet $configs 99 ) : void { 100 $tokenStream = $recognizer->getTokenStream(); 101 102 $msg = \sprintf( 103 'reportContextSensitivity d=%s, input=\'%s\'', 104 $this->getDecisionDescription($recognizer, $dfa), 105 $tokenStream === null ? '' : $tokenStream->getTextByInterval(new Interval($startIndex, $stopIndex)) 106 ); 107 108 $recognizer->notifyErrorListeners($msg); 109 } 110 111 protected function getDecisionDescription(Parser $recognizer, DFA $dfa) : string 112 { 113 $decision = $dfa->decision; 114 115 if ($dfa->atnStartState === null) { 116 throw new \RuntimeException('Unexpected null ATN Start State.'); 117 } 118 119 $ruleIndex = $dfa->atnStartState->ruleIndex; 120 121 $ruleNames = $recognizer->getRuleNames(); 122 123 if ($ruleIndex < 0 || $ruleIndex >= \count($ruleNames)) { 124 return (string) $decision; 125 } 126 127 $ruleName = $ruleNames[$ruleIndex]; 128 129 if (\strlen($ruleName) === 0) { 130 return (string) $decision; 131 } 132 133 return \sprintf('%d (%s)', $decision, $ruleName); 134 } 135 136 /** 137 * Computes the set of conflicting or ambiguous alternatives from a 138 * configuration set, if that information was not already provided by the 139 * parser. 140 * 141 * @param BitSet|null $reportedAlts The set of conflicting or ambiguous 142 * alternatives, as reported by the parser. 143 * @param ATNConfigSet $configs The conflicting or ambiguous 144 * configuration set. 145 * 146 * @return BitSet `reportedAlts` if it is not `null`, otherwise returns 147 * the set of alternatives represented in `configs`. 148 */ 149 protected function getConflictingAlts(?BitSet $reportedAlts, ATNConfigSet $configs) : BitSet 150 { 151 if ($reportedAlts !== null) { 152 return $reportedAlts; 153 } 154 155 $result = new BitSet(); 156 foreach ($configs->configs as $config) { 157 $result->add($config->alt); 158 } 159 160 return $result; 161 } 162} 163