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