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