1<?php 2 3declare(strict_types=1); 4 5namespace Antlr\Antlr4\Runtime\Dfa; 6 7use Antlr\Antlr4\Runtime\Atn\States\DecisionState; 8use Antlr\Antlr4\Runtime\Atn\States\StarLoopEntryState; 9use Antlr\Antlr4\Runtime\Utils\Set; 10use Antlr\Antlr4\Runtime\Vocabulary; 11use Antlr\Antlr4\Runtime\VocabularyImpl; 12 13final class DFA 14{ 15 /** 16 * A set of all DFA states. Use {@see Map} so we can get old state back 17 * ({@see Set} only allows you to see if it's there). 18 * 19 * @var Set; 20 */ 21 public $states; 22 23 /** @var DFAState|null */ 24 public $s0; 25 26 /** @var int */ 27 public $decision; 28 29 /** 30 * From which ATN state did we create this DFA? 31 * 32 * @var DecisionState 33 */ 34 public $atnStartState; 35 36 /** 37 * `true` if this DFA is for a precedence decision; otherwise, `false`. 38 * This is the backing field for {@see DFA::isPrecedenceDfa()}. 39 * 40 * @var bool 41 */ 42 private $precedenceDfa; 43 44 public function __construct(DecisionState $atnStartState, int $decision = 0) 45 { 46 $this->atnStartState = $atnStartState; 47 $this->decision = $decision; 48 $this->states = new Set(); 49 $this->precedenceDfa = false; 50 51 if ($atnStartState instanceof StarLoopEntryState) { 52 if ($atnStartState->isPrecedenceDecision) { 53 $this->precedenceDfa = true; 54 $precedenceState = new DFAState(); 55 $precedenceState->edges = new \SplFixedArray(); 56 $precedenceState->isAcceptState = false; 57 $precedenceState->requiresFullContext = false; 58 $this->s0 = $precedenceState; 59 } 60 } 61 } 62 63 /** 64 * Gets whether this DFA is a precedence DFA. Precedence DFAs use a special 65 * start state {@see DFA::$s0} which is not stored in {@see DFA::$states}. 66 * The {@see DFAState::$edges} array for this start state contains outgoing 67 * edges supplying individual start states corresponding to specific 68 * precedence values. 69 * 70 * @return bool `true` if this is a precedence DFA; otherwise, `false`. 71 * 72 * @see Parser::getPrecedence() 73 */ 74 public function isPrecedenceDfa() : bool 75 { 76 return $this->precedenceDfa; 77 } 78 79 /** 80 * Get the start state for a specific precedence value. 81 * 82 * @param int $precedence The current precedence. 83 * 84 * @return DFAState|null The start state corresponding to the specified 85 * precedence, or `null` if no start state exists 86 * for the specified precedence. 87 * 88 * @throws \InvalidArgumentException If this is not a precedence DFA. 89 */ 90 public function getPrecedenceStartState(int $precedence) : ?DFAState 91 { 92 if (!$this->precedenceDfa || $this->s0 === null) { 93 throw new \InvalidArgumentException('Only precedence DFAs may contain a precedence start state.'); 94 } 95 96 if ($this->s0->edges === null) { 97 throw new \RuntimeException('s0.edges cannot be null for a precedence DFA.'); 98 } 99 100 if ($precedence < 0 || $precedence >= \count($this->s0->edges)) { 101 return null; 102 } 103 104 return $this->s0->edges[$precedence] ?? null; 105 } 106 107 /** 108 * Set the start state for a specific precedence value. 109 * 110 * @param int $precedence The current precedence. 111 * @param DFAState $startState The start state corresponding to the 112 * specified precedence. 113 * 114 * @throws \InvalidArgumentException If this is not a precedence DFA. 115 */ 116 public function setPrecedenceStartState(int $precedence, DFAState $startState) : void 117 { 118 if (!$this->precedenceDfa || $this->s0 === null) { 119 throw new \InvalidArgumentException('Only precedence DFAs may contain a precedence start state.'); 120 } 121 122 if ($precedence < 0) { 123 return; 124 } 125 126 if ($this->s0->edges === null) { 127 throw new \RuntimeException('Unexpected null edges.'); 128 } 129 130 if ($precedence >= $this->s0->edges->count()) { 131 $this->s0->edges->setSize($precedence + 1); 132 } 133 134 // synchronization on s0 here is ok. when the DFA is turned into a 135 // precedence DFA, s0 will be initialized once and not updated again 136 // s0.edges is never null for a precedence DFA 137 $this->s0->edges[$precedence] = $startState; 138 } 139 140 /** 141 * Return a list of all states in this DFA, ordered by state number. 142 * 143 * @return array<DFAState> 144 */ 145 public function getStates() : array 146 { 147 $list = $this->states->getValues(); 148 149 \usort($list, static function (DFAState $a, DFAState $b) { 150 return $a->stateNumber - $b->stateNumber; 151 }); 152 153 return $list; 154 } 155 156 public function __toString() : string 157 { 158 return $this->toString(VocabularyImpl::emptyVocabulary()); 159 } 160 161 public function toString(Vocabulary $vocabulary) : string 162 { 163 if ($this->s0 === null) { 164 return ''; 165 } 166 167 $serializer = new DFASerializer($this, $vocabulary); 168 169 return (string) $serializer; 170 } 171 172 public function toLexerString() : string 173 { 174 if ($this->s0 === null) { 175 return ''; 176 } 177 178 return (new LexerDFASerializer($this))->toString(); 179 } 180} 181