xref: /plugin/combo/vendor/antlr/antlr4-php-runtime/src/PredictionContexts/PredictionContext.php (revision 37748cd8654635afbeca80942126742f0f4cc346)
1*37748cd8SNickeau<?php
2*37748cd8SNickeau
3*37748cd8SNickeaudeclare(strict_types=1);
4*37748cd8SNickeau
5*37748cd8SNickeaunamespace Antlr\Antlr4\Runtime\PredictionContexts;
6*37748cd8SNickeau
7*37748cd8SNickeauuse Antlr\Antlr4\Runtime\Atn\ATN;
8*37748cd8SNickeauuse Antlr\Antlr4\Runtime\Atn\Transitions\RuleTransition;
9*37748cd8SNickeauuse Antlr\Antlr4\Runtime\Comparison\Hashable;
10*37748cd8SNickeauuse Antlr\Antlr4\Runtime\RuleContext;
11*37748cd8SNickeauuse Antlr\Antlr4\Runtime\Utils\DoubleKeyMap;
12*37748cd8SNickeau
13*37748cd8SNickeauabstract class PredictionContext implements Hashable
14*37748cd8SNickeau{
15*37748cd8SNickeau    /** @var int */
16*37748cd8SNickeau    private static $globalNodeCount = 1;
17*37748cd8SNickeau
18*37748cd8SNickeau    /** @var int */
19*37748cd8SNickeau    public $id;
20*37748cd8SNickeau
21*37748cd8SNickeau    /**
22*37748cd8SNickeau     * Represents `$` in an array in full context mode, when `$` doesn't mean
23*37748cd8SNickeau     * wildcard: `$ + x = [$,x]`.
24*37748cd8SNickeau     *
25*37748cd8SNickeau     * Here, `$` = {@see PredictionContext::EMPTY_RETURN_STATE}.
26*37748cd8SNickeau     */
27*37748cd8SNickeau    public const EMPTY_RETURN_STATE = 0x7FFFFFFF;
28*37748cd8SNickeau
29*37748cd8SNickeau    /**
30*37748cd8SNickeau     * Stores the computed hash code of this {@see PredictionContext}. The hash
31*37748cd8SNickeau     * code is computed in parts to match the following reference algorithm.
32*37748cd8SNickeau     *
33*37748cd8SNickeau     *     private int referenceHashCode() {
34*37748cd8SNickeau     *         int hash = {@see MurmurHash::initialize}({@see PredictionContext::INITIAL_HASH});
35*37748cd8SNickeau     *
36*37748cd8SNickeau     *         for (int i = 0; i < {@see PredictionContext::size()}; i++) {
37*37748cd8SNickeau     *             hash = {@see MurmurHash::update}(hash, {@see PredictionContext::getParent()});
38*37748cd8SNickeau     *         }
39*37748cd8SNickeau     *
40*37748cd8SNickeau     *         for (int i = 0; i < {@see PredictionContext::size()}; i++) {
41*37748cd8SNickeau     *             hash = {@see MurmurHash::update}(hash, {@see PredictionContext::getReturnState()});
42*37748cd8SNickeau     *         }
43*37748cd8SNickeau     *
44*37748cd8SNickeau     *         hash = {@see MurmurHash::finish}(hash, 2 * {@see PredictionContext::size()});
45*37748cd8SNickeau     *
46*37748cd8SNickeau     *         return hash;
47*37748cd8SNickeau     *     }
48*37748cd8SNickeau     *
49*37748cd8SNickeau     * @var int|null
50*37748cd8SNickeau     */
51*37748cd8SNickeau    public $cachedHashCode;
52*37748cd8SNickeau
53*37748cd8SNickeau    public function __construct()
54*37748cd8SNickeau    {
55*37748cd8SNickeau        $this->id = self::$globalNodeCount++;
56*37748cd8SNickeau    }
57*37748cd8SNickeau
58*37748cd8SNickeau    public static function empty() : EmptyPredictionContext
59*37748cd8SNickeau    {
60*37748cd8SNickeau        static $empty;
61*37748cd8SNickeau
62*37748cd8SNickeau        if ($empty === null) {
63*37748cd8SNickeau            static::$globalNodeCount--;
64*37748cd8SNickeau            $empty = new EmptyPredictionContext();
65*37748cd8SNickeau            $empty->id = 0;
66*37748cd8SNickeau        }
67*37748cd8SNickeau
68*37748cd8SNickeau        return $empty;
69*37748cd8SNickeau    }
70*37748cd8SNickeau
71*37748cd8SNickeau    /**
72*37748cd8SNickeau     * Convert a {@see RuleContext} tree to a {@see PredictionContext} graph.
73*37748cd8SNickeau     * Return {@see PredictionContext::empty()} if `outerContext` is empty or null.
74*37748cd8SNickeau     */
75*37748cd8SNickeau    public static function fromRuleContext(ATN $atn, ?RuleContext $outerContext) : PredictionContext
76*37748cd8SNickeau    {
77*37748cd8SNickeau        if ($outerContext === null) {
78*37748cd8SNickeau            $outerContext = RuleContext::emptyContext();
79*37748cd8SNickeau        }
80*37748cd8SNickeau
81*37748cd8SNickeau        // If we are in RuleContext of start rule, s, then PredictionContext
82*37748cd8SNickeau        // is EMPTY. Nobody called us. (if we are empty, return empty)
83*37748cd8SNickeau        if ($outerContext->getParent() === null || $outerContext === RuleContext::emptyContext()) {
84*37748cd8SNickeau            return self::empty();
85*37748cd8SNickeau        }
86*37748cd8SNickeau
87*37748cd8SNickeau        // If we have a parent, convert it to a PredictionContext graph
88*37748cd8SNickeau        $parent = self::fromRuleContext($atn, $outerContext->getParent());
89*37748cd8SNickeau        $state = $atn->states[$outerContext->invokingState];
90*37748cd8SNickeau        $transition = $state->getTransition(0);
91*37748cd8SNickeau
92*37748cd8SNickeau        if (!$transition instanceof RuleTransition) {
93*37748cd8SNickeau            throw new \RuntimeException('Unexpected transition type.');
94*37748cd8SNickeau        }
95*37748cd8SNickeau
96*37748cd8SNickeau        return SingletonPredictionContext::create($parent, $transition->followState->stateNumber);
97*37748cd8SNickeau    }
98*37748cd8SNickeau
99*37748cd8SNickeau    /**
100*37748cd8SNickeau     * This means only the {@see PredictionContext::empty()} (wildcard? not sure)
101*37748cd8SNickeau     * context is in set.
102*37748cd8SNickeau     */
103*37748cd8SNickeau    public function isEmpty() : bool
104*37748cd8SNickeau    {
105*37748cd8SNickeau        return $this === self::empty();
106*37748cd8SNickeau    }
107*37748cd8SNickeau
108*37748cd8SNickeau    public function hasEmptyPath() : bool
109*37748cd8SNickeau    {
110*37748cd8SNickeau        return $this->getReturnState($this->getLength() - 1) === self::EMPTY_RETURN_STATE;
111*37748cd8SNickeau    }
112*37748cd8SNickeau
113*37748cd8SNickeau    public function hashCode() : int
114*37748cd8SNickeau    {
115*37748cd8SNickeau        if ($this->cachedHashCode === null) {
116*37748cd8SNickeau            $this->cachedHashCode = $this->computeHashCode();
117*37748cd8SNickeau        }
118*37748cd8SNickeau
119*37748cd8SNickeau        return $this->cachedHashCode;
120*37748cd8SNickeau    }
121*37748cd8SNickeau
122*37748cd8SNickeau    abstract protected function computeHashCode() : int;
123*37748cd8SNickeau    abstract public function getLength() : int;
124*37748cd8SNickeau    abstract public function getParent(int $index) : ?self;
125*37748cd8SNickeau    abstract public function getReturnState(int $index) : int;
126*37748cd8SNickeau    abstract public function __toString() : string;
127*37748cd8SNickeau
128*37748cd8SNickeau    public static function merge(
129*37748cd8SNickeau        PredictionContext $a,
130*37748cd8SNickeau        PredictionContext $b,
131*37748cd8SNickeau        bool $rootIsWildcard,
132*37748cd8SNickeau        ?DoubleKeyMap $mergeCache
133*37748cd8SNickeau    ) : PredictionContext {
134*37748cd8SNickeau        // share same graph if both same
135*37748cd8SNickeau        if ($a->equals($b)) {
136*37748cd8SNickeau            return $a;
137*37748cd8SNickeau        }
138*37748cd8SNickeau
139*37748cd8SNickeau        if ($a instanceof SingletonPredictionContext && $b instanceof SingletonPredictionContext) {
140*37748cd8SNickeau            return self::mergeSingletons($a, $b, $rootIsWildcard, $mergeCache);
141*37748cd8SNickeau        }
142*37748cd8SNickeau
143*37748cd8SNickeau        // At least one of a or b is array
144*37748cd8SNickeau        // If one is $ and rootIsWildcard, return $ as * wildcard
145*37748cd8SNickeau        if ($rootIsWildcard) {
146*37748cd8SNickeau            if ($a instanceof EmptyPredictionContext) {
147*37748cd8SNickeau                return $a;
148*37748cd8SNickeau            }
149*37748cd8SNickeau
150*37748cd8SNickeau            if ($b instanceof EmptyPredictionContext) {
151*37748cd8SNickeau                return $b;
152*37748cd8SNickeau            }
153*37748cd8SNickeau        }
154*37748cd8SNickeau
155*37748cd8SNickeau        // convert singleton so both are arrays to normalize
156*37748cd8SNickeau        if ($a instanceof SingletonPredictionContext) {
157*37748cd8SNickeau            $a = ArrayPredictionContext::fromOne($a);
158*37748cd8SNickeau        }
159*37748cd8SNickeau
160*37748cd8SNickeau        if ($b instanceof SingletonPredictionContext) {
161*37748cd8SNickeau            $b = ArrayPredictionContext::fromOne($b);
162*37748cd8SNickeau        }
163*37748cd8SNickeau
164*37748cd8SNickeau        if (!$a instanceof ArrayPredictionContext || !$b instanceof ArrayPredictionContext) {
165*37748cd8SNickeau            throw new \RuntimeException('Unexpected transition type.');
166*37748cd8SNickeau        }
167*37748cd8SNickeau
168*37748cd8SNickeau        return self::mergeArrays($a, $b, $rootIsWildcard, $mergeCache);
169*37748cd8SNickeau    }
170*37748cd8SNickeau
171*37748cd8SNickeau    /**
172*37748cd8SNickeau     * Merge two {@see SingletonPredictionContext} instances.
173*37748cd8SNickeau     *
174*37748cd8SNickeau     * Stack tops equal, parents merge is same; return left graph.
175*37748cd8SNickeau     *
176*37748cd8SNickeau     * Same stack top, parents differ; merge parents giving array node, then
177*37748cd8SNickeau     * remainders of those graphs. A new root node is created to point to the
178*37748cd8SNickeau     * merged parents.
179*37748cd8SNickeau     *
180*37748cd8SNickeau     * Different stack tops pointing to same parent. Make array node for the
181*37748cd8SNickeau     * root where both element in the root point to the same (original)
182*37748cd8SNickeau     * parent.
183*37748cd8SNickeau     *
184*37748cd8SNickeau     * Different stack tops pointing to different parents. Make array node for
185*37748cd8SNickeau     * the root where each element points to the corresponding original
186*37748cd8SNickeau     * parent.
187*37748cd8SNickeau     *
188*37748cd8SNickeau     * @param SingletonPredictionContext $a              The first {@see SingletonPredictionContext}
189*37748cd8SNickeau     * @param SingletonPredictionContext $b              The second {@see SingletonPredictionContext}
190*37748cd8SNickeau     * @param bool                       $rootIsWildcard `true` if this is a local-context merge,
191*37748cd8SNickeau     *                                                   otherwise false to indicate a full-context merge
192*37748cd8SNickeau     */
193*37748cd8SNickeau    public static function mergeSingletons(
194*37748cd8SNickeau        SingletonPredictionContext $a,
195*37748cd8SNickeau        SingletonPredictionContext $b,
196*37748cd8SNickeau        bool $rootIsWildcard,
197*37748cd8SNickeau        ?DoubleKeyMap $mergeCache
198*37748cd8SNickeau    ) : PredictionContext {
199*37748cd8SNickeau        if ($mergeCache !== null) {
200*37748cd8SNickeau            $previous = $mergeCache->getByTwoKeys($a, $b);
201*37748cd8SNickeau
202*37748cd8SNickeau            if ($previous !== null) {
203*37748cd8SNickeau                return $previous;
204*37748cd8SNickeau            }
205*37748cd8SNickeau
206*37748cd8SNickeau            $previous = $mergeCache->getByTwoKeys($b, $a);
207*37748cd8SNickeau
208*37748cd8SNickeau            if ($previous !== null) {
209*37748cd8SNickeau                return $previous;
210*37748cd8SNickeau            }
211*37748cd8SNickeau        }
212*37748cd8SNickeau
213*37748cd8SNickeau        $rootMerge = self::mergeRoot($a, $b, $rootIsWildcard);
214*37748cd8SNickeau
215*37748cd8SNickeau        if ($rootMerge !== null) {
216*37748cd8SNickeau            if ($mergeCache !== null) {
217*37748cd8SNickeau                $mergeCache->set($a, $b, $rootMerge);
218*37748cd8SNickeau            }
219*37748cd8SNickeau
220*37748cd8SNickeau            return $rootMerge;
221*37748cd8SNickeau        }
222*37748cd8SNickeau
223*37748cd8SNickeau        if ($a->returnState === $b->returnState) {
224*37748cd8SNickeau            if ($a->parent === null || $b->parent === null) {
225*37748cd8SNickeau                throw new \RuntimeException('Unexpected null parents.');
226*37748cd8SNickeau            }
227*37748cd8SNickeau
228*37748cd8SNickeau            $parent = self::merge($a->parent, $b->parent, $rootIsWildcard, $mergeCache);
229*37748cd8SNickeau
230*37748cd8SNickeau            // If parent is same as existing a or b parent or reduced to a parent, return it
231*37748cd8SNickeau            if ($parent === $a->parent) {
232*37748cd8SNickeau                return $a; // ax + bx = ax, if a=b
233*37748cd8SNickeau            }
234*37748cd8SNickeau
235*37748cd8SNickeau            if ($parent === $b->parent) {
236*37748cd8SNickeau                return $b; // ax + bx = bx, if a=b
237*37748cd8SNickeau            }
238*37748cd8SNickeau
239*37748cd8SNickeau            // Else: ax + ay = a'[x,y]
240*37748cd8SNickeau            //
241*37748cd8SNickeau            // Merge parents x and y, giving array node with x,y then remainders
242*37748cd8SNickeau            // of those graphs. dup a, a' points at merged array new joined parent
243*37748cd8SNickeau            // so create new singleton pointing to it, a'
244*37748cd8SNickeau            $spc = SingletonPredictionContext::create($parent, $a->returnState);
245*37748cd8SNickeau
246*37748cd8SNickeau            if ($mergeCache !== null) {
247*37748cd8SNickeau                $mergeCache->set($a, $b, $spc);
248*37748cd8SNickeau            }
249*37748cd8SNickeau            return $spc;
250*37748cd8SNickeau        } else {
251*37748cd8SNickeau            // a != b payloads differ
252*37748cd8SNickeau            // see if we can collapse parents due to $+x parents if local ctx
253*37748cd8SNickeau            $singleParent = null;
254*37748cd8SNickeau
255*37748cd8SNickeau            if ($a === $b || ($a->parent !== null && $a->parent === $b->parent)) {
256*37748cd8SNickeau                // ax +
257*37748cd8SNickeau                // bx =
258*37748cd8SNickeau                // [a,b]x
259*37748cd8SNickeau                $singleParent = $a->parent;
260*37748cd8SNickeau            }
261*37748cd8SNickeau
262*37748cd8SNickeau            if ($singleParent !== null) {
263*37748cd8SNickeau                // parents are same
264*37748cd8SNickeau                // sort payloads and use same parent
265*37748cd8SNickeau                $payloads = [$a->returnState, $b->returnState];
266*37748cd8SNickeau
267*37748cd8SNickeau                if ($a->returnState > $b->returnState) {
268*37748cd8SNickeau                    $payloads[0] = $b->returnState;
269*37748cd8SNickeau                    $payloads[1] = $a->returnState;
270*37748cd8SNickeau                }
271*37748cd8SNickeau
272*37748cd8SNickeau                $parents = [$singleParent, $singleParent];
273*37748cd8SNickeau                $apc = new ArrayPredictionContext($parents, $payloads);
274*37748cd8SNickeau
275*37748cd8SNickeau                if ($mergeCache !== null) {
276*37748cd8SNickeau                    $mergeCache->set($a, $b, $apc);
277*37748cd8SNickeau                }
278*37748cd8SNickeau
279*37748cd8SNickeau                return $apc;
280*37748cd8SNickeau            }
281*37748cd8SNickeau
282*37748cd8SNickeau            // parents differ and can't merge them. Just pack together
283*37748cd8SNickeau            // into array; can't merge.
284*37748cd8SNickeau            // ax + by = [ax,by]
285*37748cd8SNickeau            $payloads = [$a->returnState, $b->returnState];
286*37748cd8SNickeau            $parents = [$a->parent, $b->parent];
287*37748cd8SNickeau
288*37748cd8SNickeau            if ($a->returnState > $b->returnState) {
289*37748cd8SNickeau                // sort by payload
290*37748cd8SNickeau                $payloads[0] = $b->returnState;
291*37748cd8SNickeau                $payloads[1] = $a->returnState;
292*37748cd8SNickeau                $parents = [$b->parent, $a->parent];
293*37748cd8SNickeau            }
294*37748cd8SNickeau
295*37748cd8SNickeau            $a_ = new ArrayPredictionContext($parents, $payloads);
296*37748cd8SNickeau
297*37748cd8SNickeau            if ($mergeCache !== null) {
298*37748cd8SNickeau                $mergeCache->set($a, $b, $a_);
299*37748cd8SNickeau            }
300*37748cd8SNickeau
301*37748cd8SNickeau            return $a_;
302*37748cd8SNickeau        }
303*37748cd8SNickeau    }
304*37748cd8SNickeau
305*37748cd8SNickeau    /**
306*37748cd8SNickeau     * Handle case where at least one of `a` or `b` is
307*37748cd8SNickeau     * {@see PredictionContext::empty()}. In the following diagrams, the symbol
308*37748cd8SNickeau     * `$` is used to represent {@see PredictionContext::empty()}.
309*37748cd8SNickeau     *
310*37748cd8SNickeau     * Local-Context Merges
311*37748cd8SNickeau     *
312*37748cd8SNickeau     * These local-context merge operations are used when `rootIsWildcard`
313*37748cd8SNickeau     * is true.
314*37748cd8SNickeau     *
315*37748cd8SNickeau     * {@see PredictionContext::empty()} is superset of any graph; return
316*37748cd8SNickeau     * {@see PredictionContext::empty()}.
317*37748cd8SNickeau     *
318*37748cd8SNickeau     * [[img src="images/LocalMerge_EmptyRoot.svg" type="image/svg+xml"]]
319*37748cd8SNickeau     *
320*37748cd8SNickeau     * {@see PredictionContext::empty()} and anything is `#EMPTY`, so merged parent is
321*37748cd8SNickeau     * `#EMPTY`; return left graph
322*37748cd8SNickeau     *
323*37748cd8SNickeau     * [[img src="images/LocalMerge_EmptyParent.svg" type="image/svg+xml"]]
324*37748cd8SNickeau     *
325*37748cd8SNickeau     * Special case of last merge if local context.
326*37748cd8SNickeau     *
327*37748cd8SNickeau     * [[img src="images/LocalMerge_DiffRoots.svg" type="image/svg+xml"]]
328*37748cd8SNickeau     *
329*37748cd8SNickeau     * Full-Context Merges
330*37748cd8SNickeau     *
331*37748cd8SNickeau     * These full-context merge operations are used when `rootIsWildcard`
332*37748cd8SNickeau     * is false.
333*37748cd8SNickeau     *
334*37748cd8SNickeau     * Must keep all contexts; {@see PredictionContext::empty()} in array is
335*37748cd8SNickeau     * a special value (and null parent).
336*37748cd8SNickeau     *
337*37748cd8SNickeau     * [[img src="images/FullMerge_EmptyRoot.svg" type="image/svg+xml"]]
338*37748cd8SNickeau     *
339*37748cd8SNickeau     * [[img src="images/FullMerge_SameRoot.svg" type="image/svg+xml"]]
340*37748cd8SNickeau     */
341*37748cd8SNickeau    public static function mergeRoot(
342*37748cd8SNickeau        SingletonPredictionContext $a,
343*37748cd8SNickeau        SingletonPredictionContext $b,
344*37748cd8SNickeau        bool $rootIsWildcard
345*37748cd8SNickeau    ) : ?PredictionContext {
346*37748cd8SNickeau        if ($rootIsWildcard) {
347*37748cd8SNickeau            if ($a === self::empty()) {
348*37748cd8SNickeau                return self::empty();// // + b =//
349*37748cd8SNickeau            }
350*37748cd8SNickeau
351*37748cd8SNickeau            if ($b === self::empty()) {
352*37748cd8SNickeau                return self::empty();// a +// =//
353*37748cd8SNickeau            }
354*37748cd8SNickeau        } else {
355*37748cd8SNickeau            if ($a === self::empty() && $b === self::empty()) {
356*37748cd8SNickeau                return self::empty();// $ + $ = $
357*37748cd8SNickeau            }
358*37748cd8SNickeau
359*37748cd8SNickeau            if ($a === self::empty()) {
360*37748cd8SNickeau                // $ + x = [$,x]
361*37748cd8SNickeau                $payloads = [$b->returnState, self::EMPTY_RETURN_STATE];
362*37748cd8SNickeau                $parents = [$b->parent, null];
363*37748cd8SNickeau
364*37748cd8SNickeau                return new ArrayPredictionContext($parents, $payloads);
365*37748cd8SNickeau            }
366*37748cd8SNickeau
367*37748cd8SNickeau            if ($b === self::empty()) {
368*37748cd8SNickeau                // x + $ = [$,x] ($ is always first if present)
369*37748cd8SNickeau                $payloads = [$a->returnState, self::EMPTY_RETURN_STATE];
370*37748cd8SNickeau                $parents = [$a->parent, null];
371*37748cd8SNickeau
372*37748cd8SNickeau                return new ArrayPredictionContext($parents, $payloads);
373*37748cd8SNickeau            }
374*37748cd8SNickeau        }
375*37748cd8SNickeau
376*37748cd8SNickeau        return null;
377*37748cd8SNickeau    }
378*37748cd8SNickeau
379*37748cd8SNickeau    /**
380*37748cd8SNickeau     * Merge two {@see ArrayPredictionContext} instances.
381*37748cd8SNickeau     *
382*37748cd8SNickeau     * Different tops, different parents.
383*37748cd8SNickeau     *
384*37748cd8SNickeau     * [[img src="images/ArrayMerge_DiffTopDiffPar.svg" type="image/svg+xml"]]
385*37748cd8SNickeau     *
386*37748cd8SNickeau     * Shared top, same parents.
387*37748cd8SNickeau     *
388*37748cd8SNickeau     * [[img src="images/ArrayMerge_ShareTopSamePar.svg" type="image/svg+xml"]]
389*37748cd8SNickeau     *
390*37748cd8SNickeau     * Shared top, different parents.
391*37748cd8SNickeau     *
392*37748cd8SNickeau     * [[img src="images/ArrayMerge_ShareTopDiffPar.svg" type="image/svg+xml"]]
393*37748cd8SNickeau     *
394*37748cd8SNickeau     * Shared top, all shared parents.
395*37748cd8SNickeau     *
396*37748cd8SNickeau     * [[img src="images/ArrayMerge_ShareTopSharePar.svg" type="image/svg+xml"]]
397*37748cd8SNickeau     *
398*37748cd8SNickeau     * Equal tops, merge parents and reduce top to
399*37748cd8SNickeau     * {@see SingletonPredictionContext}.
400*37748cd8SNickeau     *
401*37748cd8SNickeau     * [[img src="images/ArrayMerge_EqualTop.svg" type="image/svg+xml"]]
402*37748cd8SNickeau     */
403*37748cd8SNickeau    public static function mergeArrays(
404*37748cd8SNickeau        ArrayPredictionContext $a,
405*37748cd8SNickeau        ArrayPredictionContext $b,
406*37748cd8SNickeau        bool $rootIsWildcard,
407*37748cd8SNickeau        ?DoubleKeyMap $mergeCache
408*37748cd8SNickeau    ) : PredictionContext {
409*37748cd8SNickeau        if ($mergeCache !== null) {
410*37748cd8SNickeau            $previous = $mergeCache->getByTwoKeys($a, $b);
411*37748cd8SNickeau
412*37748cd8SNickeau            if ($previous !== null) {
413*37748cd8SNickeau                return $previous;
414*37748cd8SNickeau            }
415*37748cd8SNickeau
416*37748cd8SNickeau            $previous = $mergeCache->getByTwoKeys($b, $a);
417*37748cd8SNickeau
418*37748cd8SNickeau            if ($previous !== null) {
419*37748cd8SNickeau                return $previous;
420*37748cd8SNickeau            }
421*37748cd8SNickeau        }
422*37748cd8SNickeau
423*37748cd8SNickeau        // merge sorted payloads a + b => M
424*37748cd8SNickeau        $i = 0;// walks a
425*37748cd8SNickeau        $j = 0;// walks b
426*37748cd8SNickeau        $k = 0;// walks target M array
427*37748cd8SNickeau
428*37748cd8SNickeau        $mergedReturnStates = [];
429*37748cd8SNickeau        $mergedParents = [];
430*37748cd8SNickeau
431*37748cd8SNickeau        // walk and merge to yield mergedParents, mergedReturnStates
432*37748cd8SNickeau        while ($i < \count($a->returnStates) && $j < \count($b->returnStates)) {
433*37748cd8SNickeau            $a_parent = $a->parents[$i];
434*37748cd8SNickeau            $b_parent = $b->parents[$j];
435*37748cd8SNickeau
436*37748cd8SNickeau            if ($a->returnStates[$i] === $b->returnStates[$j]) {
437*37748cd8SNickeau                // same payload (stack tops are equal), must yield merged singleton
438*37748cd8SNickeau                $payload = $a->returnStates[$i];
439*37748cd8SNickeau
440*37748cd8SNickeau                // $+$ = $
441*37748cd8SNickeau                $bothDollars = $payload === self::EMPTY_RETURN_STATE && $a_parent === null && $b_parent === null;
442*37748cd8SNickeau                $ax_ax = ($a_parent !== null && $b_parent !== null && $a_parent->equals($b_parent));// ax+ax
443*37748cd8SNickeau                // ->
444*37748cd8SNickeau                // ax
445*37748cd8SNickeau
446*37748cd8SNickeau                if ($bothDollars || $ax_ax) {
447*37748cd8SNickeau                    $mergedParents[$k] = $a_parent;// choose left
448*37748cd8SNickeau                    $mergedReturnStates[$k] = $payload;
449*37748cd8SNickeau                } else {
450*37748cd8SNickeau                    if ($a_parent === null || $b_parent === null) {
451*37748cd8SNickeau                        throw new \RuntimeException('Unexpected null parents.');
452*37748cd8SNickeau                    }
453*37748cd8SNickeau
454*37748cd8SNickeau                    // ax+ay -> a'[x,y]
455*37748cd8SNickeau                    $mergedParent = self::merge($a_parent, $b_parent, $rootIsWildcard, $mergeCache);
456*37748cd8SNickeau                    $mergedParents[$k] = $mergedParent;
457*37748cd8SNickeau                    $mergedReturnStates[$k] = $payload;
458*37748cd8SNickeau                }
459*37748cd8SNickeau
460*37748cd8SNickeau                $i++;// hop over left one as usual
461*37748cd8SNickeau                $j++;// but also skip one in right side since we merge
462*37748cd8SNickeau            } elseif ($a->returnStates[$i] < $b->returnStates[$j]) {
463*37748cd8SNickeau                // copy a[i] to M
464*37748cd8SNickeau                $mergedParents[$k] = $a_parent;
465*37748cd8SNickeau                $mergedReturnStates[$k] = $a->returnStates[$i];
466*37748cd8SNickeau                $i++;
467*37748cd8SNickeau            } else {
468*37748cd8SNickeau                // b > a, copy b[j] to M
469*37748cd8SNickeau                $mergedParents[$k] = $b_parent;
470*37748cd8SNickeau                $mergedReturnStates[$k] = $b->returnStates[$j];
471*37748cd8SNickeau                $j++;
472*37748cd8SNickeau            }
473*37748cd8SNickeau
474*37748cd8SNickeau            $k++;
475*37748cd8SNickeau        }
476*37748cd8SNickeau
477*37748cd8SNickeau        // copy over any payloads remaining in either array
478*37748cd8SNickeau        if ($i < \count($a->returnStates)) {
479*37748cd8SNickeau            for ($p = $i, $count = \count($a->returnStates); $p < $count; $p++) {
480*37748cd8SNickeau                $mergedParents[$k] = $a->parents[$p];
481*37748cd8SNickeau                $mergedReturnStates[$k] = $a->returnStates[$p];
482*37748cd8SNickeau                $k++;
483*37748cd8SNickeau            }
484*37748cd8SNickeau        } else {
485*37748cd8SNickeau            for ($p = $j, $count = \count($b->returnStates); $p < $count; $p++) {
486*37748cd8SNickeau                $mergedParents[$k] = $b->parents[$p];
487*37748cd8SNickeau                $mergedReturnStates[$k] = $b->returnStates[$p];
488*37748cd8SNickeau                $k++;
489*37748cd8SNickeau            }
490*37748cd8SNickeau        }
491*37748cd8SNickeau
492*37748cd8SNickeau        // trim merged if we combined a few that had same stack tops
493*37748cd8SNickeau        if ($k < \count($mergedParents)) {
494*37748cd8SNickeau            // write index < last position; trim
495*37748cd8SNickeau            if ($k === 1) {
496*37748cd8SNickeau                // for just one merged element, return singleton top
497*37748cd8SNickeau                $a_ = SingletonPredictionContext::create($mergedParents[0], $mergedReturnStates[0]);
498*37748cd8SNickeau
499*37748cd8SNickeau                if ($mergeCache !== null) {
500*37748cd8SNickeau                    $mergeCache->set($a, $b, $a_);
501*37748cd8SNickeau                }
502*37748cd8SNickeau
503*37748cd8SNickeau                return $a_;
504*37748cd8SNickeau            }
505*37748cd8SNickeau
506*37748cd8SNickeau            $mergedParents = \array_slice($mergedParents, 0, $k);
507*37748cd8SNickeau            $mergedReturnStates = \array_slice($mergedReturnStates, 0, $k);
508*37748cd8SNickeau        }
509*37748cd8SNickeau
510*37748cd8SNickeau        self::combineCommonParents($mergedParents);
511*37748cd8SNickeau
512*37748cd8SNickeau        $M = new ArrayPredictionContext($mergedParents, $mergedReturnStates);
513*37748cd8SNickeau
514*37748cd8SNickeau        // if we created same array as a or b, return that instead
515*37748cd8SNickeau        // TODO: track whether this is possible above during merge sort for speed
516*37748cd8SNickeau        if ($M === $a) {
517*37748cd8SNickeau            if ($mergeCache !== null) {
518*37748cd8SNickeau                $mergeCache->set($a, $b, $a);
519*37748cd8SNickeau            }
520*37748cd8SNickeau
521*37748cd8SNickeau            return $a;
522*37748cd8SNickeau        }
523*37748cd8SNickeau
524*37748cd8SNickeau        if ($M === $b) {
525*37748cd8SNickeau            if ($mergeCache !== null) {
526*37748cd8SNickeau                $mergeCache->set($a, $b, $b);
527*37748cd8SNickeau            }
528*37748cd8SNickeau
529*37748cd8SNickeau            return $b;
530*37748cd8SNickeau        }
531*37748cd8SNickeau
532*37748cd8SNickeau        if ($mergeCache !== null) {
533*37748cd8SNickeau            $mergeCache->set($a, $b, $M);
534*37748cd8SNickeau        }
535*37748cd8SNickeau
536*37748cd8SNickeau        return $M;
537*37748cd8SNickeau    }
538*37748cd8SNickeau
539*37748cd8SNickeau    /**
540*37748cd8SNickeau     * @param array<PredictionContext> $parents
541*37748cd8SNickeau     */
542*37748cd8SNickeau    protected static function combineCommonParents(array &$parents) : void
543*37748cd8SNickeau    {
544*37748cd8SNickeau        $uniqueParents = new \SplObjectStorage();
545*37748cd8SNickeau
546*37748cd8SNickeau        foreach ($parents as $parent) {
547*37748cd8SNickeau            if (!$uniqueParents->contains($parent)) {
548*37748cd8SNickeau                $uniqueParents[$parent] = $parent;
549*37748cd8SNickeau            }
550*37748cd8SNickeau        }
551*37748cd8SNickeau
552*37748cd8SNickeau        foreach ($parents as $i => $parent) {
553*37748cd8SNickeau            $parents[$i] = $uniqueParents[$parent];
554*37748cd8SNickeau        }
555*37748cd8SNickeau    }
556*37748cd8SNickeau
557*37748cd8SNickeau    /**
558*37748cd8SNickeau     * @param array<PredictionContext|null> $visited
559*37748cd8SNickeau     */
560*37748cd8SNickeau    public static function getCachedPredictionContext(
561*37748cd8SNickeau        PredictionContext $context,
562*37748cd8SNickeau        PredictionContextCache $contextCache,
563*37748cd8SNickeau        array &$visited
564*37748cd8SNickeau    ) : self {
565*37748cd8SNickeau        if ($context->isEmpty()) {
566*37748cd8SNickeau            return $context;
567*37748cd8SNickeau        }
568*37748cd8SNickeau
569*37748cd8SNickeau        $existing = $visited[\spl_object_id($context)] ?? null;
570*37748cd8SNickeau
571*37748cd8SNickeau        if ($existing !== null) {
572*37748cd8SNickeau            return $existing;
573*37748cd8SNickeau        }
574*37748cd8SNickeau
575*37748cd8SNickeau        $existing = $contextCache->get($context);
576*37748cd8SNickeau
577*37748cd8SNickeau        if ($existing !== null) {
578*37748cd8SNickeau            $visited[\spl_object_id($context)] = $existing;
579*37748cd8SNickeau
580*37748cd8SNickeau            return $existing;
581*37748cd8SNickeau        }
582*37748cd8SNickeau
583*37748cd8SNickeau        $changed = false;
584*37748cd8SNickeau        $parents = [];
585*37748cd8SNickeau        for ($i = 0; $i < $context->getLength(); $i++) {
586*37748cd8SNickeau            $parentContext = $context->getParent($i);
587*37748cd8SNickeau
588*37748cd8SNickeau            if ($parentContext === null) {
589*37748cd8SNickeau                continue;
590*37748cd8SNickeau            }
591*37748cd8SNickeau
592*37748cd8SNickeau            $parent = self::getCachedPredictionContext($parentContext, $contextCache, $visited);
593*37748cd8SNickeau
594*37748cd8SNickeau            if ($changed || ($parentContext !== null && !$parent->equals($parentContext))) {
595*37748cd8SNickeau                if (!$changed) {
596*37748cd8SNickeau                    $parents = [];
597*37748cd8SNickeau
598*37748cd8SNickeau                    for ($j = 0; $j < $context->getLength(); $j++) {
599*37748cd8SNickeau                        $parents[$j] = $context->getParent($j);
600*37748cd8SNickeau                    }
601*37748cd8SNickeau
602*37748cd8SNickeau                    $changed = true;
603*37748cd8SNickeau                }
604*37748cd8SNickeau
605*37748cd8SNickeau                $parents[$i] = $parent;
606*37748cd8SNickeau            }
607*37748cd8SNickeau        }
608*37748cd8SNickeau
609*37748cd8SNickeau        if (!$changed) {
610*37748cd8SNickeau            $contextCache->add($context);
611*37748cd8SNickeau
612*37748cd8SNickeau            $visited[\spl_object_id($context)] = $context;
613*37748cd8SNickeau
614*37748cd8SNickeau            return $context;
615*37748cd8SNickeau        }
616*37748cd8SNickeau
617*37748cd8SNickeau        $updated = null;
618*37748cd8SNickeau
619*37748cd8SNickeau        if (\count($parents) === 0) {
620*37748cd8SNickeau            $updated = self::empty();
621*37748cd8SNickeau        } elseif (\count($parents) === 1) {
622*37748cd8SNickeau            $updated = SingletonPredictionContext::create($parents[0], $context->getReturnState(0));
623*37748cd8SNickeau        } else {
624*37748cd8SNickeau            if (!$context instanceof ArrayPredictionContext) {
625*37748cd8SNickeau                throw new \RuntimeException('Unexpected context type.');
626*37748cd8SNickeau            }
627*37748cd8SNickeau
628*37748cd8SNickeau            $updated = new ArrayPredictionContext($parents, $context->returnStates);
629*37748cd8SNickeau        }
630*37748cd8SNickeau
631*37748cd8SNickeau        $contextCache->add($updated);
632*37748cd8SNickeau        $visited[\spl_object_id($updated)] = $updated;
633*37748cd8SNickeau        $visited[\spl_object_id($context)] = $updated;
634*37748cd8SNickeau
635*37748cd8SNickeau        return $updated;
636*37748cd8SNickeau    }
637*37748cd8SNickeau
638*37748cd8SNickeau    public function __clone()
639*37748cd8SNickeau    {
640*37748cd8SNickeau        $this->cachedHashCode = null;
641*37748cd8SNickeau    }
642*37748cd8SNickeau}
643