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