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