xref: /plugin/combo/vendor/antlr/antlr4-php-runtime/src/Atn/ATNConfigSet.php (revision 37748cd8654635afbeca80942126742f0f4cc346)
1*37748cd8SNickeau<?php
2*37748cd8SNickeau
3*37748cd8SNickeaudeclare(strict_types=1);
4*37748cd8SNickeau
5*37748cd8SNickeaunamespace Antlr\Antlr4\Runtime\Atn;
6*37748cd8SNickeau
7*37748cd8SNickeauuse Antlr\Antlr4\Runtime\Atn\SemanticContexts\SemanticContext;
8*37748cd8SNickeauuse Antlr\Antlr4\Runtime\Comparison\Equality;
9*37748cd8SNickeauuse Antlr\Antlr4\Runtime\Comparison\Equivalence;
10*37748cd8SNickeauuse Antlr\Antlr4\Runtime\Comparison\Hashable;
11*37748cd8SNickeauuse Antlr\Antlr4\Runtime\Comparison\Hasher;
12*37748cd8SNickeauuse Antlr\Antlr4\Runtime\PredictionContexts\PredictionContext;
13*37748cd8SNickeauuse Antlr\Antlr4\Runtime\Utils\BitSet;
14*37748cd8SNickeauuse Antlr\Antlr4\Runtime\Utils\DoubleKeyMap;
15*37748cd8SNickeauuse Antlr\Antlr4\Runtime\Utils\Set;
16*37748cd8SNickeau
17*37748cd8SNickeau/**
18*37748cd8SNickeau * Specialized {@see Set} of `{@see ATNConfig}`s that can track info
19*37748cd8SNickeau * about the set, with support for combining similar configurations using
20*37748cd8SNickeau * a graph-structured stack.
21*37748cd8SNickeau */
22*37748cd8SNickeauclass ATNConfigSet implements Hashable
23*37748cd8SNickeau{
24*37748cd8SNickeau    /**
25*37748cd8SNickeau     * Indicates that the set of configurations is read-only. Do not
26*37748cd8SNickeau     * allow any code to manipulate the set; DFA states will point at
27*37748cd8SNickeau     * the sets and they must not change. This does not protect the other
28*37748cd8SNickeau     * fields; in particular, conflictingAlts is set after
29*37748cd8SNickeau     * we've made this readonly.
30*37748cd8SNickeau     *
31*37748cd8SNickeau     * @var bool
32*37748cd8SNickeau     */
33*37748cd8SNickeau    protected $readOnly = false;
34*37748cd8SNickeau
35*37748cd8SNickeau    /**
36*37748cd8SNickeau     * All configs but hashed by (s, i, _, pi) not including context. Wiped out
37*37748cd8SNickeau     * when we go readonly as this set becomes a DFA state.
38*37748cd8SNickeau     *
39*37748cd8SNickeau     * @var Set|null
40*37748cd8SNickeau     */
41*37748cd8SNickeau    public $configLookup;
42*37748cd8SNickeau
43*37748cd8SNickeau    /**
44*37748cd8SNickeau     * Track the elements as they are added to the set; supports get(i).
45*37748cd8SNickeau     *
46*37748cd8SNickeau     * @var array<ATNConfig>
47*37748cd8SNickeau     */
48*37748cd8SNickeau    public $configs = [];
49*37748cd8SNickeau
50*37748cd8SNickeau    /** @var int */
51*37748cd8SNickeau    public $uniqueAlt = 0;
52*37748cd8SNickeau
53*37748cd8SNickeau    /**
54*37748cd8SNickeau     * Currently this is only used when we detect SLL conflict; this does
55*37748cd8SNickeau     * not necessarily represent the ambiguous alternatives. In fact, I should
56*37748cd8SNickeau     * also point out that this seems to include predicated alternatives that
57*37748cd8SNickeau     * have predicates that evaluate to false. Computed in computeTargetState().
58*37748cd8SNickeau     *
59*37748cd8SNickeau     * @var BitSet|null
60*37748cd8SNickeau     */
61*37748cd8SNickeau    protected $conflictingAlts;
62*37748cd8SNickeau
63*37748cd8SNickeau    /**
64*37748cd8SNickeau     * Used in parser and lexer. In lexer, it indicates we hit a pred
65*37748cd8SNickeau     * while computing a closure operation. Don't make a DFA state from this.
66*37748cd8SNickeau     *
67*37748cd8SNickeau     * @var bool
68*37748cd8SNickeau     */
69*37748cd8SNickeau    public $hasSemanticContext = false;
70*37748cd8SNickeau
71*37748cd8SNickeau    /** @var bool */
72*37748cd8SNickeau    public $dipsIntoOuterContext = false;
73*37748cd8SNickeau
74*37748cd8SNickeau    /**
75*37748cd8SNickeau     * Indicates that this configuration set is part of a full context LL
76*37748cd8SNickeau     * prediction. It will be used to determine how to merge $. With SLL it's
77*37748cd8SNickeau     * a wildcard whereas it is not for LL context merge.
78*37748cd8SNickeau     *
79*37748cd8SNickeau     * @var bool
80*37748cd8SNickeau     */
81*37748cd8SNickeau    public $fullCtx;
82*37748cd8SNickeau
83*37748cd8SNickeau    /** @var int|null */
84*37748cd8SNickeau    private $cachedHashCode;
85*37748cd8SNickeau
86*37748cd8SNickeau    public function __construct(bool $fullCtx = true)
87*37748cd8SNickeau    {
88*37748cd8SNickeau        /*
89*37748cd8SNickeau         * The reason that we need this is because we don't want the hash map to
90*37748cd8SNickeau         * use the standard hash code and equals. We need all configurations with
91*37748cd8SNickeau         * the same `(s,i,_,semctx)` to be equal. Unfortunately, this key
92*37748cd8SNickeau         * effectively doubles the number of objects associated with ATNConfigs.
93*37748cd8SNickeau         * The other solution is to use a hash table that lets us specify the
94*37748cd8SNickeau         * equals/hashcode operation. All configs but hashed by (s, i, _, pi)
95*37748cd8SNickeau         * not including context. Wiped out when we go readonly as this se
96*37748cd8SNickeau         * becomes a DFA state.
97*37748cd8SNickeau         */
98*37748cd8SNickeau        $this->configLookup = new Set(new class implements Equivalence {
99*37748cd8SNickeau            public function equivalent(Hashable $left, Hashable $right) : bool
100*37748cd8SNickeau            {
101*37748cd8SNickeau                if ($left === $right) {
102*37748cd8SNickeau                    return true;
103*37748cd8SNickeau                }
104*37748cd8SNickeau
105*37748cd8SNickeau                if (!$left instanceof ATNConfig || !$right instanceof ATNConfig) {
106*37748cd8SNickeau                    return false;
107*37748cd8SNickeau                }
108*37748cd8SNickeau
109*37748cd8SNickeau                return $left->alt === $right->alt
110*37748cd8SNickeau                    && $left->semanticContext->equals($right->semanticContext)
111*37748cd8SNickeau                    && Equality::equals($left->state, $right->state);
112*37748cd8SNickeau            }
113*37748cd8SNickeau
114*37748cd8SNickeau            public function hash(Hashable $value) : int
115*37748cd8SNickeau            {
116*37748cd8SNickeau                return $value->hashCode();
117*37748cd8SNickeau            }
118*37748cd8SNickeau
119*37748cd8SNickeau            public function equals(object $other) : bool
120*37748cd8SNickeau            {
121*37748cd8SNickeau                return $other instanceof self;
122*37748cd8SNickeau            }
123*37748cd8SNickeau        });
124*37748cd8SNickeau
125*37748cd8SNickeau        $this->fullCtx = $fullCtx;
126*37748cd8SNickeau    }
127*37748cd8SNickeau
128*37748cd8SNickeau    /**
129*37748cd8SNickeau     * Adding a new config means merging contexts with existing configs for
130*37748cd8SNickeau     * `(s, i, pi, _)`, where `s` is the {@see ATNConfig::$state}, `i` is the
131*37748cd8SNickeau     * {@see ATNConfig::$alt}, and `pi` is the {@see ATNConfig::$semanticContext}.
132*37748cd8SNickeau     * We use `(s,i,pi)` as key.
133*37748cd8SNickeau     *
134*37748cd8SNickeau     * This method updates {@see ATNConfigSet::$dipsIntoOuterContext} and
135*37748cd8SNickeau     * {@see ATNConfigSet::$hasSemanticContext} when necessary.
136*37748cd8SNickeau     *
137*37748cd8SNickeau     * @throws \InvalidArgumentException
138*37748cd8SNickeau     */
139*37748cd8SNickeau    public function add(ATNConfig $config, ?DoubleKeyMap $mergeCache = null) : bool
140*37748cd8SNickeau    {
141*37748cd8SNickeau        if ($this->readOnly || $this->configLookup === null) {
142*37748cd8SNickeau            throw new \InvalidArgumentException('This set is readonly.');
143*37748cd8SNickeau        }
144*37748cd8SNickeau
145*37748cd8SNickeau        if ($config->semanticContext !== SemanticContext::none()) {
146*37748cd8SNickeau            $this->hasSemanticContext = true;
147*37748cd8SNickeau        }
148*37748cd8SNickeau
149*37748cd8SNickeau        if ($config->reachesIntoOuterContext > 0) {
150*37748cd8SNickeau            $this->dipsIntoOuterContext = true;
151*37748cd8SNickeau        }
152*37748cd8SNickeau
153*37748cd8SNickeau        if ($this->configLookup === null) {
154*37748cd8SNickeau            throw new \RuntimeException('This set is readonly.');
155*37748cd8SNickeau        }
156*37748cd8SNickeau
157*37748cd8SNickeau        /** @var ATNConfig $existing */
158*37748cd8SNickeau        $existing = $this->configLookup->getOrAdd($config);
159*37748cd8SNickeau
160*37748cd8SNickeau        if ($existing->equals($config)) {
161*37748cd8SNickeau            $this->cachedHashCode = null;
162*37748cd8SNickeau
163*37748cd8SNickeau            $this->configs[] = $config; // track order here
164*37748cd8SNickeau
165*37748cd8SNickeau            return true;
166*37748cd8SNickeau        }
167*37748cd8SNickeau
168*37748cd8SNickeau        // A previous (s,i,pi,_), merge with it and save result
169*37748cd8SNickeau        $rootIsWildcard = !$this->fullCtx;
170*37748cd8SNickeau
171*37748cd8SNickeau        if ($existing->context === null || $config->context === null) {
172*37748cd8SNickeau            throw new \RuntimeException('Unexpected null context.');
173*37748cd8SNickeau        }
174*37748cd8SNickeau
175*37748cd8SNickeau        $merged = PredictionContext::merge($existing->context, $config->context, $rootIsWildcard, $mergeCache);
176*37748cd8SNickeau
177*37748cd8SNickeau        // No need to check for existing->context, config->context in cache
178*37748cd8SNickeau        // since only way to create new graphs is "call rule" and here. We
179*37748cd8SNickeau        // cache at both places.
180*37748cd8SNickeau        $existing->reachesIntoOuterContext = \max(
181*37748cd8SNickeau            $existing->reachesIntoOuterContext,
182*37748cd8SNickeau            $config->reachesIntoOuterContext
183*37748cd8SNickeau        );
184*37748cd8SNickeau
185*37748cd8SNickeau        // Make sure to preserve the precedence filter suppression during the merge
186*37748cd8SNickeau        if ($config->isPrecedenceFilterSuppressed()) {
187*37748cd8SNickeau            $existing->setPrecedenceFilterSuppressed(true);
188*37748cd8SNickeau        }
189*37748cd8SNickeau
190*37748cd8SNickeau        // Replace context; no need to alt mapping
191*37748cd8SNickeau        $existing->context = $merged;
192*37748cd8SNickeau
193*37748cd8SNickeau        return true;
194*37748cd8SNickeau    }
195*37748cd8SNickeau
196*37748cd8SNickeau    /**
197*37748cd8SNickeau     * Return a List holding list of configs.
198*37748cd8SNickeau     *
199*37748cd8SNickeau     * @return array<ATNConfig>
200*37748cd8SNickeau     */
201*37748cd8SNickeau    public function elements() : array
202*37748cd8SNickeau    {
203*37748cd8SNickeau        return $this->configs;
204*37748cd8SNickeau    }
205*37748cd8SNickeau
206*37748cd8SNickeau    public function getStates() : Set
207*37748cd8SNickeau    {
208*37748cd8SNickeau        $states = new Set();
209*37748cd8SNickeau        foreach ($this->configs as $config) {
210*37748cd8SNickeau            if ($config !== null) {
211*37748cd8SNickeau                $states->add($config->state);
212*37748cd8SNickeau            }
213*37748cd8SNickeau        }
214*37748cd8SNickeau
215*37748cd8SNickeau        return $states;
216*37748cd8SNickeau    }
217*37748cd8SNickeau
218*37748cd8SNickeau    /**
219*37748cd8SNickeau     * Gets the complete set of represented alternatives for the configurationc set.
220*37748cd8SNickeau     *
221*37748cd8SNickeau     * @return BitSet The set of represented alternatives in this configuration set.
222*37748cd8SNickeau     */
223*37748cd8SNickeau    public function getAlts() : BitSet
224*37748cd8SNickeau    {
225*37748cd8SNickeau        $alts = new BitSet();
226*37748cd8SNickeau        foreach ($this->configs as $config) {
227*37748cd8SNickeau            if ($config !== null) {
228*37748cd8SNickeau                $alts->add($config->alt);
229*37748cd8SNickeau            }
230*37748cd8SNickeau        }
231*37748cd8SNickeau
232*37748cd8SNickeau        return $alts;
233*37748cd8SNickeau    }
234*37748cd8SNickeau
235*37748cd8SNickeau    /**
236*37748cd8SNickeau     * @return array<SemanticContext>
237*37748cd8SNickeau     */
238*37748cd8SNickeau    public function getPredicates() : array
239*37748cd8SNickeau    {
240*37748cd8SNickeau        $predicates = [];
241*37748cd8SNickeau        foreach ($this->configs as $config) {
242*37748cd8SNickeau            if ($config->semanticContext !== SemanticContext::none()) {
243*37748cd8SNickeau                $predicates[] = $config->semanticContext;
244*37748cd8SNickeau            }
245*37748cd8SNickeau        }
246*37748cd8SNickeau
247*37748cd8SNickeau        return $predicates;
248*37748cd8SNickeau    }
249*37748cd8SNickeau
250*37748cd8SNickeau    public function get(int $index) : ATNConfig
251*37748cd8SNickeau    {
252*37748cd8SNickeau        return $this->configs[$index];
253*37748cd8SNickeau    }
254*37748cd8SNickeau
255*37748cd8SNickeau    public function optimizeConfigs(ATNSimulator $interpreter) : void
256*37748cd8SNickeau    {
257*37748cd8SNickeau        if ($this->readOnly || $this->configLookup === null) {
258*37748cd8SNickeau            throw new \InvalidArgumentException('This set is readonly');
259*37748cd8SNickeau        }
260*37748cd8SNickeau
261*37748cd8SNickeau        if ($this->configLookup->isEmpty()) {
262*37748cd8SNickeau            return;
263*37748cd8SNickeau        }
264*37748cd8SNickeau
265*37748cd8SNickeau        foreach ($this->configs as $config) {
266*37748cd8SNickeau            if ($config !== null && $config->context !== null) {
267*37748cd8SNickeau                $config->context = $interpreter->getCachedContext($config->context);
268*37748cd8SNickeau            }
269*37748cd8SNickeau        }
270*37748cd8SNickeau    }
271*37748cd8SNickeau
272*37748cd8SNickeau    /**
273*37748cd8SNickeau     * @param array<ATNConfig> $configs
274*37748cd8SNickeau     */
275*37748cd8SNickeau    public function addAll(array $configs) : void
276*37748cd8SNickeau    {
277*37748cd8SNickeau        foreach ($configs as $config) {
278*37748cd8SNickeau            $this->add($config);
279*37748cd8SNickeau        }
280*37748cd8SNickeau    }
281*37748cd8SNickeau
282*37748cd8SNickeau    public function equals(object $other) : bool
283*37748cd8SNickeau    {
284*37748cd8SNickeau        if ($this === $other) {
285*37748cd8SNickeau            return true;
286*37748cd8SNickeau        }
287*37748cd8SNickeau
288*37748cd8SNickeau        if (!$other instanceof self) {
289*37748cd8SNickeau            return false;
290*37748cd8SNickeau        }
291*37748cd8SNickeau
292*37748cd8SNickeau        return $this->fullCtx === $other->fullCtx
293*37748cd8SNickeau            && $this->uniqueAlt === $other->uniqueAlt
294*37748cd8SNickeau            && $this->hasSemanticContext === $other->hasSemanticContext
295*37748cd8SNickeau            && $this->dipsIntoOuterContext === $other->dipsIntoOuterContext
296*37748cd8SNickeau            && Equality::equals($this->configs, $other->configs)
297*37748cd8SNickeau            && Equality::equals($this->conflictingAlts, $other->conflictingAlts);
298*37748cd8SNickeau    }
299*37748cd8SNickeau
300*37748cd8SNickeau    public function hashCode() : int
301*37748cd8SNickeau    {
302*37748cd8SNickeau        if (!$this->isReadOnly()) {
303*37748cd8SNickeau            return Hasher::hash($this->configs);
304*37748cd8SNickeau        }
305*37748cd8SNickeau
306*37748cd8SNickeau        if ($this->cachedHashCode === null) {
307*37748cd8SNickeau            $this->cachedHashCode = Hasher::hash($this->configs);
308*37748cd8SNickeau        }
309*37748cd8SNickeau
310*37748cd8SNickeau        return $this->cachedHashCode;
311*37748cd8SNickeau    }
312*37748cd8SNickeau
313*37748cd8SNickeau    public function getLength() : int
314*37748cd8SNickeau    {
315*37748cd8SNickeau        return \count($this->configs);
316*37748cd8SNickeau    }
317*37748cd8SNickeau
318*37748cd8SNickeau    public function isEmpty() : bool
319*37748cd8SNickeau    {
320*37748cd8SNickeau        return $this->getLength() === 0;
321*37748cd8SNickeau    }
322*37748cd8SNickeau
323*37748cd8SNickeau    public function contains(object $item) : bool
324*37748cd8SNickeau    {
325*37748cd8SNickeau        if ($this->configLookup === null) {
326*37748cd8SNickeau            throw new \InvalidArgumentException('This method is not implemented for readonly sets.');
327*37748cd8SNickeau        }
328*37748cd8SNickeau
329*37748cd8SNickeau        return $this->configLookup->contains($item);
330*37748cd8SNickeau    }
331*37748cd8SNickeau
332*37748cd8SNickeau    public function containsFast(ATNConfig $item) : bool
333*37748cd8SNickeau    {
334*37748cd8SNickeau        return $this->contains($item);
335*37748cd8SNickeau    }
336*37748cd8SNickeau
337*37748cd8SNickeau    public function getIterator() : \Iterator
338*37748cd8SNickeau    {
339*37748cd8SNickeau        return new \ArrayIterator($this->configs);
340*37748cd8SNickeau    }
341*37748cd8SNickeau
342*37748cd8SNickeau    public function clear() : void
343*37748cd8SNickeau    {
344*37748cd8SNickeau        if ($this->readOnly) {
345*37748cd8SNickeau            throw new \InvalidArgumentException('This set is readonly');
346*37748cd8SNickeau        }
347*37748cd8SNickeau
348*37748cd8SNickeau        $this->configs = [];
349*37748cd8SNickeau        $this->cachedHashCode = -1;
350*37748cd8SNickeau        $this->configLookup = new Set();
351*37748cd8SNickeau    }
352*37748cd8SNickeau
353*37748cd8SNickeau    public function isReadOnly() : bool
354*37748cd8SNickeau    {
355*37748cd8SNickeau        return $this->readOnly;
356*37748cd8SNickeau    }
357*37748cd8SNickeau
358*37748cd8SNickeau    public function setReadonly(bool $readOnly) : void
359*37748cd8SNickeau    {
360*37748cd8SNickeau        $this->readOnly = $readOnly;
361*37748cd8SNickeau
362*37748cd8SNickeau        if ($readOnly) {
363*37748cd8SNickeau            $this->configLookup = null; // can't mod, no need for lookup cache
364*37748cd8SNickeau        }
365*37748cd8SNickeau    }
366*37748cd8SNickeau
367*37748cd8SNickeau    public function getConflictingAlts() : ?BitSet
368*37748cd8SNickeau    {
369*37748cd8SNickeau        return $this->conflictingAlts;
370*37748cd8SNickeau    }
371*37748cd8SNickeau
372*37748cd8SNickeau    public function setConflictingAlts(BitSet $conflictingAlts) : void
373*37748cd8SNickeau    {
374*37748cd8SNickeau        $this->conflictingAlts = $conflictingAlts;
375*37748cd8SNickeau    }
376*37748cd8SNickeau
377*37748cd8SNickeau    public function __toString() : string
378*37748cd8SNickeau    {
379*37748cd8SNickeau        return \sprintf(
380*37748cd8SNickeau            '[%s]%s%s%s%s',
381*37748cd8SNickeau            \implode(', ', $this->configs),
382*37748cd8SNickeau            $this->hasSemanticContext ? ',hasSemanticContext=' . $this->hasSemanticContext : '',
383*37748cd8SNickeau            $this->uniqueAlt !== ATN::INVALID_ALT_NUMBER ? ',uniqueAlt=' . $this->uniqueAlt : '',
384*37748cd8SNickeau            $this->conflictingAlts !== null ? ',conflictingAlts=' . $this->conflictingAlts : '',
385*37748cd8SNickeau            $this->dipsIntoOuterContext ? ',dipsIntoOuterContext' : ''
386*37748cd8SNickeau        );
387*37748cd8SNickeau    }
388*37748cd8SNickeau}
389