SetBased /
antlr-php-runtime
| 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
Loading history...
|
|||
| 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. Loading history...
|
|||
| 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 |