From f408ba38e8fa53f25dbffe53d1693f385b1c0896 Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Tue, 20 Dec 2022 19:42:22 -0500 Subject: [PATCH 01/18] Fix for #12 -- Allowed memory size of 134217728 bytes exhausted. --- src/Atn/ATNConfigSet.php | 26 +++++---- src/Atn/OrderedATNConfigSet.php | 28 ++++++++- src/ParserTraceListener.php | 7 ++- .../ArrayPredictionContext.php | 2 +- src/PredictionContexts/PredictionContext.php | 58 ++++++++++++------- src/Utils/Map.php | 32 ++++++++++ 6 files changed, 115 insertions(+), 38 deletions(-) diff --git a/src/Atn/ATNConfigSet.php b/src/Atn/ATNConfigSet.php index 319cd6b..215d17e 100644 --- a/src/Atn/ATNConfigSet.php +++ b/src/Atn/ATNConfigSet.php @@ -13,6 +13,7 @@ use Antlr\Antlr4\Runtime\Utils\BitSet; use Antlr\Antlr4\Runtime\Utils\DoubleKeyMap; use Antlr\Antlr4\Runtime\Utils\Set; +use Antlr\Antlr4\Runtime\Utils\Map; /** * Specialized {@see Set} of `{@see ATNConfig}`s that can track info @@ -34,7 +35,7 @@ class ATNConfigSet implements Hashable * All configs but hashed by (s, i, _, pi) not including context. Wiped out * when we go readonly as this set becomes a DFA state. */ - public ?Set $configLookup = null; + public ?Map $configLookup = null; /** * Track the elements as they are added to the set; supports get(i). @@ -82,25 +83,26 @@ public function __construct(bool $fullCtx = true) * not including context. Wiped out when we go readonly as this se * becomes a DFA state. */ - $this->configLookup = new Set(new class implements Equivalence { + $this->configLookup = new Map(new class implements Equivalence { public function equivalent(Hashable $left, Hashable $right): bool { if ($left === $right) { return true; } - if (!$left instanceof ATNConfig || !$right instanceof ATNConfig) { - return false; - } - - return $left->alt === $right->alt - && $left->semanticContext->equals($right->semanticContext) - && Equality::equals($left->state, $right->state); + if ($left == null || $right == null) return false; + return $left->state->stateNumber == $right->state->stateNumber + && $left->alt == $right->alt + && $left->semanticContext->equals($right->semanticContext); } public function hash(Hashable $value): int { - return $value->hashCode(); + return Hasher::hash( + $value->state->stateNumber, + $value->alt, + $value->semanticContext, + ); } public function equals(object $other): bool @@ -140,7 +142,7 @@ public function add(ATNConfig $config, ?DoubleKeyMap $mergeCache = null): bool /** @var ATNConfig $existing */ $existing = $this->configLookup->getOrAdd($config); - if ($existing->equals($config)) { + if ($existing === $config) { $this->cachedHashCode = null; $this->configs[] = $config; // track order here @@ -326,7 +328,7 @@ public function clear(): void $this->configs = []; $this->cachedHashCode = -1; - $this->configLookup = new Set(); + $this->configLookup = new Map(); } public function isReadOnly(): bool diff --git a/src/Atn/OrderedATNConfigSet.php b/src/Atn/OrderedATNConfigSet.php index 6eeb804..88e772c 100644 --- a/src/Atn/OrderedATNConfigSet.php +++ b/src/Atn/OrderedATNConfigSet.php @@ -3,8 +3,11 @@ declare(strict_types=1); namespace Antlr\Antlr4\Runtime\Atn; - -use Antlr\Antlr4\Runtime\Utils\Set; +use Antlr\Antlr4\Runtime\Comparison\Equality; +use Antlr\Antlr4\Runtime\Comparison\Equivalence; +use Antlr\Antlr4\Runtime\Comparison\Hashable; +use Antlr\Antlr4\Runtime\Comparison\Hasher; +use Antlr\Antlr4\Runtime\Utils\Map; final class OrderedATNConfigSet extends ATNConfigSet { @@ -12,6 +15,25 @@ public function __construct() { parent::__construct(); - $this->configLookup = new Set(); + $this->configLookup = new Map(new class implements Equivalence { + public function equivalent(Hashable $left, Hashable $right): bool + { + if ($left === $right) { + return true; + } + if ($left == null || $right == null) return false; + return $left->equals($right); + } + + public function hash(Hashable $value): int + { + return $value->hashCode(); + } + + public function equals(object $other): bool + { + return $other instanceof self; + } + }); } } diff --git a/src/ParserTraceListener.php b/src/ParserTraceListener.php index d194b11..7081d23 100644 --- a/src/ParserTraceListener.php +++ b/src/ParserTraceListener.php @@ -27,15 +27,17 @@ public function enterEveryRule(ParserRuleContext $context): void $this->parser->getRuleNames()[$context->getRuleIndex()], $token === null? '' : $token->getText() ?? '', ); + print("\n"); } public function visitTerminal(TerminalNode $node): void { - echo \sprintf( + print \sprintf( 'consume %s rule %s', $node->getSymbol(), $this->parser->getCurrentRuleName(), ); + print("\n"); } public function exitEveryRule(ParserRuleContext $context): void @@ -43,11 +45,12 @@ public function exitEveryRule(ParserRuleContext $context): void $stream = $this->parser->getTokenStream(); $token = $stream?->LT(1); - echo \sprintf( + print \sprintf( 'exit %s, LT(1)=%s', $this->parser->getRuleNames()[$context->getRuleIndex()], $token === null? '' : $token->getText() ?? '', ); + print("\n"); } public function visitErrorNode(ErrorNode $node): void diff --git a/src/PredictionContexts/ArrayPredictionContext.php b/src/PredictionContexts/ArrayPredictionContext.php index a174752..e96646a 100644 --- a/src/PredictionContexts/ArrayPredictionContext.php +++ b/src/PredictionContexts/ArrayPredictionContext.php @@ -86,7 +86,7 @@ public function equals(object $other): bool return false; } - if ($this->returnStates === $other->returnStates) { + if (!($this->returnStates === $other->returnStates)) { return false; } diff --git a/src/PredictionContexts/PredictionContext.php b/src/PredictionContexts/PredictionContext.php index 5e2c97c..0ba9084 100644 --- a/src/PredictionContexts/PredictionContext.php +++ b/src/PredictionContexts/PredictionContext.php @@ -11,6 +11,7 @@ use Antlr\Antlr4\Runtime\LoggerProvider; use Antlr\Antlr4\Runtime\RuleContext; use Antlr\Antlr4\Runtime\Utils\DoubleKeyMap; +use Antlr\Antlr4\Runtime\Utils\Map; abstract class PredictionContext implements Hashable { @@ -294,6 +295,11 @@ public static function mergeSingletons( $payloads[1] = $a->returnState; $parents = [$b->parent, $a->parent]; } + else { + $payloads[0] = $a->returnState; + $payloads[1] = $b->returnState; + $parents = [$a->parent, $b->parent]; + } $a_ = new ArrayPredictionContext($parents, $payloads); @@ -426,7 +432,13 @@ public static function mergeArrays( $k = 0;// walks target M array $mergedReturnStates = []; + for ($ini = 0; $ini < \count($a->returnStates) + \count($b->returnStates); $ini++) { + $mergedReturnStates[$ini] = null; + } $mergedParents = []; + for ($ini = 0; $ini < \count($a->returnStates) + \count($b->returnStates); $ini++) { + $mergedParents[$ini] = null; + } // walk and merge to yield mergedParents, mergedReturnStates while ($i < \count($a->returnStates) && $j < \count($b->returnStates)) { @@ -475,14 +487,18 @@ public static function mergeArrays( } // copy over any payloads remaining in either array - if ($i < \count($a->returnStates)) { - for ($p = $i, $count = \count($a->returnStates); $p < $count; $p++) { + if ($i < \count($a->returnStates)) + { + for ($p = $i; $p < \count($a->returnStates); $p++) + { $mergedParents[$k] = $a->parents[$p]; $mergedReturnStates[$k] = $a->returnStates[$p]; $k++; } - } else { - for ($p = $j, $count = \count($b->returnStates); $p < $count; $p++) { + } + else { + for ($p = $j; $p < \count($b->returnStates); $p++) + { $mergedParents[$k] = $b->parents[$p]; $mergedReturnStates[$k] = $b->returnStates[$p]; $k++; @@ -492,28 +508,27 @@ public static function mergeArrays( // trim merged if we combined a few that had same stack tops if ($k < \count($mergedParents)) { // write index < last position; trim - if ($k === 1) { - // for just one merged element, return singleton top + if ($k === 1) + { // for just one merged element, return singleton top $a_ = SingletonPredictionContext::create($mergedParents[0], $mergedReturnStates[0]); - if ($mergeCache !== null) { - $mergeCache->set($a, $b, $a_); - } + if ($mergeCache !== null) $mergeCache->set($a, $b, $a_); return $a_; } + // mergedParents = Arrays.CopyOf(mergedParents, k); $mergedParents = \array_slice($mergedParents, 0, $k); + // mergedReturnStates = Arrays.CopyOf(mergedReturnStates, k); $mergedReturnStates = \array_slice($mergedReturnStates, 0, $k); } - self::combineCommonParents($mergedParents); $M = new ArrayPredictionContext($mergedParents, $mergedReturnStates); // if we created same array as a or b, return that instead // TODO: track whether this is possible above during merge sort for speed - if ($M === $a) { + if ($M->equals($a)) { if ($mergeCache !== null) { $mergeCache->set($a, $b, $a); } @@ -529,7 +544,7 @@ public static function mergeArrays( return $a; } - if ($M === $b) { + if ($M->equals($b)) { if ($mergeCache !== null) { $mergeCache->set($a, $b, $b); } @@ -545,19 +560,20 @@ public static function mergeArrays( return $b; } - if ($mergeCache !== null) { - $mergeCache->set($a, $b, $M); - } + self::combineCommonParents($mergedParents); if (ParserATNSimulator::$traceAtnSimulation) { LoggerProvider::getLogger() - ->debug('mergeArrays a={a},b={b} -> M', [ + ->debug('mergeArrays a={a},b={b} -> {M}', [ 'a' => $a->__toString(), 'b' => $b->__toString(), 'M' => $M->__toString(), ]); } + if ($mergeCache !== null) { + $mergeCache->set($a, $b, $M); + } return $M; } @@ -566,16 +582,18 @@ public static function mergeArrays( */ protected static function combineCommonParents(array &$parents): void { - $uniqueParents = new \SplObjectStorage(); + $uniqueParents = new Map(); foreach ($parents as $parent) { - if (!$uniqueParents->contains($parent)) { - $uniqueParents[$parent] = $parent; + if ($parent != null && !$uniqueParents->contains($parent)) { + // don't replace. + $uniqueParents->put($parent, $parent); } } foreach ($parents as $i => $parent) { - $parents[$i] = $uniqueParents[$parent]; + if ($parent != null) + $parents[$i] = $uniqueParents->get($parent); } } diff --git a/src/Utils/Map.php b/src/Utils/Map.php index 5bab247..dbeeb06 100644 --- a/src/Utils/Map.php +++ b/src/Utils/Map.php @@ -28,6 +28,10 @@ public function __construct(?Equivalence $equivalence = null) $this->equivalence = $equivalence ?? new DefaultEquivalence(); } + public function isEmpty(): bool + { + return $this->count() === 0; + } public function count(): int { return $this->size; @@ -212,4 +216,32 @@ private static function isEqual(mixed $left, mixed $right): bool return $left === $right; } + public function getOrAdd(Hashable $value): Hashable + { + $key = $value; + $hash = $this->equivalence->hash($value); + + if (!isset($this->table[$hash])) { + $this->table[$hash] = []; + } + + foreach ($this->table[$hash] as $index => [$entryKey, $entryValue]) { + if ($this->equivalence->equivalent($key, $entryKey)) { + return $entryValue; + } + } + + foreach ($this->table[$hash] as $index => [$entryKey]) { + if ($this->equivalence->equivalent($key, $entryKey)) { + $this->table[$hash][$index] = [$key, $value]; + return $value; + } + } + + $this->table[$hash][] = [$key, $value]; + + $this->size++; + + return $value; + } } From 815349fa18c17872e467ad7816df28549345ff6e Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Wed, 21 Dec 2022 11:23:57 -0500 Subject: [PATCH 02/18] Fix bug in incorrect access of field reachesIntoOuterContext not through accessor that does bitmap adjustment. --- src/Atn/ATNConfigSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Atn/ATNConfigSet.php b/src/Atn/ATNConfigSet.php index 215d17e..650d269 100644 --- a/src/Atn/ATNConfigSet.php +++ b/src/Atn/ATNConfigSet.php @@ -135,7 +135,7 @@ public function add(ATNConfig $config, ?DoubleKeyMap $mergeCache = null): bool $this->hasSemanticContext = true; } - if ($config->reachesIntoOuterContext > 0) { + if ($config->getOuterContextDepth() > 0) { $this->dipsIntoOuterContext = true; } From 169a4d259eaee4b54c666ec8bb27985ca2260b2f Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Wed, 21 Dec 2022 19:58:06 -0500 Subject: [PATCH 03/18] Fixing parser ATN tracing to be identical with CSharp. Perfect!! --- src/Atn/ATNConfig.php | 2 +- src/Atn/ATNConfigSet.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Atn/ATNConfig.php b/src/Atn/ATNConfig.php index 7e9c9b9..92b8894 100644 --- a/src/Atn/ATNConfig.php +++ b/src/Atn/ATNConfig.php @@ -180,7 +180,7 @@ public function __toString(): string $this->semanticContext->equals(SemanticContext::none()) ? '' : ',' . $this->semanticContext, - $this->reachesIntoOuterContext > 0 ? ',up=' . $this->reachesIntoOuterContext : '', + $this->reachesIntoOuterContext > 0 ? ',up=' . $this->getOuterContextDepth() : '', ); } } diff --git a/src/Atn/ATNConfigSet.php b/src/Atn/ATNConfigSet.php index 650d269..65b8b98 100644 --- a/src/Atn/ATNConfigSet.php +++ b/src/Atn/ATNConfigSet.php @@ -360,7 +360,7 @@ public function __toString(): string return \sprintf( '[%s]%s%s%s%s', \implode(', ', $this->configs), - $this->hasSemanticContext ? ',hasSemanticContext=' . $this->hasSemanticContext : '', + $this->hasSemanticContext ? ',hasSemanticContext=true' : '', $this->uniqueAlt !== ATN::INVALID_ALT_NUMBER ? ',uniqueAlt=' . $this->uniqueAlt : '', $this->conflictingAlts !== null ? ',conflictingAlts=' . $this->conflictingAlts : '', $this->dipsIntoOuterContext ? ',dipsIntoOuterContext' : '', From 566bfc5c61a4e4bc927cec518043114610538a5e Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Fri, 23 Dec 2022 10:08:58 -0500 Subject: [PATCH 04/18] Fix newline characters. --- src/ParserTraceListener.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ParserTraceListener.php b/src/ParserTraceListener.php index 7081d23..4e2e24c 100644 --- a/src/ParserTraceListener.php +++ b/src/ParserTraceListener.php @@ -27,17 +27,17 @@ public function enterEveryRule(ParserRuleContext $context): void $this->parser->getRuleNames()[$context->getRuleIndex()], $token === null? '' : $token->getText() ?? '', ); - print("\n"); + echo \PHP_EOL; } public function visitTerminal(TerminalNode $node): void { - print \sprintf( + echo \sprintf( 'consume %s rule %s', $node->getSymbol(), $this->parser->getCurrentRuleName(), ); - print("\n"); + echo \PHP_EOL; } public function exitEveryRule(ParserRuleContext $context): void @@ -45,12 +45,12 @@ public function exitEveryRule(ParserRuleContext $context): void $stream = $this->parser->getTokenStream(); $token = $stream?->LT(1); - print \sprintf( + echo \sprintf( 'exit %s, LT(1)=%s', $this->parser->getRuleNames()[$context->getRuleIndex()], $token === null? '' : $token->getText() ?? '', ); - print("\n"); + echo \PHP_EOL; } public function visitErrorNode(ErrorNode $node): void From f62f6376e2fec7712a81cf31f8e00b57ab6777ce Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Fri, 23 Dec 2022 11:47:38 -0500 Subject: [PATCH 05/18] Let's see if that works on analysis errors. --- src/Atn/ATNConfigSet.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Atn/ATNConfigSet.php b/src/Atn/ATNConfigSet.php index 65b8b98..df0fce2 100644 --- a/src/Atn/ATNConfigSet.php +++ b/src/Atn/ATNConfigSet.php @@ -91,13 +91,12 @@ public function equivalent(Hashable $left, Hashable $right): bool } if ($left == null || $right == null) return false; - return $left->state->stateNumber == $right->state->stateNumber - && $left->alt == $right->alt - && $left->semanticContext->equals($right->semanticContext); + return true; } public function hash(Hashable $value): int { + if (!($value instanceof ATNConfig)) return 0; return Hasher::hash( $value->state->stateNumber, $value->alt, From 59e8eaa2af105d6ece28be1add59b1d30a233f48 Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Fri, 23 Dec 2022 11:51:26 -0500 Subject: [PATCH 06/18] OK, that did. Try this fix for more analysis errors. --- src/Atn/ATNConfigSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Atn/ATNConfigSet.php b/src/Atn/ATNConfigSet.php index df0fce2..8bafb36 100644 --- a/src/Atn/ATNConfigSet.php +++ b/src/Atn/ATNConfigSet.php @@ -90,7 +90,7 @@ public function equivalent(Hashable $left, Hashable $right): bool return true; } - if ($left == null || $right == null) return false; + if ($left === null || $right === null) return false; return true; } From b47d2a5b0e9d18164d1a08c7a01c2aab8971d5be Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Fri, 23 Dec 2022 11:57:31 -0500 Subject: [PATCH 07/18] Try this too. --- src/Atn/OrderedATNConfigSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Atn/OrderedATNConfigSet.php b/src/Atn/OrderedATNConfigSet.php index 88e772c..42fdaa9 100644 --- a/src/Atn/OrderedATNConfigSet.php +++ b/src/Atn/OrderedATNConfigSet.php @@ -21,7 +21,7 @@ public function equivalent(Hashable $left, Hashable $right): bool if ($left === $right) { return true; } - if ($left == null || $right == null) return false; + if ($left === null || $right === null) return false; return $left->equals($right); } From cf5162908bb1848ca0a7a80ea160e1c52f9618a2 Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Sat, 24 Dec 2022 08:06:16 -0500 Subject: [PATCH 08/18] Break out ConfigHashSet since that's exactly how it's done with Java and CSharp targets. --- composer.json | 2 +- composer.lock | 14 ++-- src/Atn/ATNConfigSet.php | 32 ++------ src/Atn/ConfigHashSet.php | 80 ++++++++++++++++++++ src/Atn/OrderedATNConfigSet.php | 9 ++- src/PredictionContexts/PredictionContext.php | 10 ++- src/Utils/Map.php | 58 ++++---------- 7 files changed, 121 insertions(+), 84 deletions(-) create mode 100644 src/Atn/ConfigHashSet.php diff --git a/composer.json b/composer.json index 5160d18..7e114e6 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "require-dev": { "ergebnis/composer-normalize": "^2.15", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.4", + "phpstan/phpstan": "^1.9", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-strict-rules": "^1.1", "slevomat/coding-standard": "^7.0", diff --git a/composer.lock b/composer.lock index ec737fb..3e3a7c6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6d73951acc3119aabd404dbbe0967053", + "content-hash": "367dc3d722296f940d17b2cfb8efbc38", "packages": [ { "name": "psr/log", @@ -622,16 +622,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.9.2", + "version": "1.9.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa" + "reference": "d03bccee595e2146b7c9d174486b84f4dc61b0f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d6fdf01c53978b6429f1393ba4afeca39cc68afa", - "reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d03bccee595e2146b7c9d174486b84f4dc61b0f2", + "reference": "d03bccee595e2146b7c9d174486b84f4dc61b0f2", "shasum": "" }, "require": { @@ -661,7 +661,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.9.2" + "source": "https://github.com/phpstan/phpstan/tree/1.9.4" }, "funding": [ { @@ -677,7 +677,7 @@ "type": "tidelift" } ], - "time": "2022-11-10T09:56:11+00:00" + "time": "2022-12-17T13:33:52+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", diff --git a/src/Atn/ATNConfigSet.php b/src/Atn/ATNConfigSet.php index 8bafb36..9710d7f 100644 --- a/src/Atn/ATNConfigSet.php +++ b/src/Atn/ATNConfigSet.php @@ -35,7 +35,8 @@ class ATNConfigSet implements Hashable * All configs but hashed by (s, i, _, pi) not including context. Wiped out * when we go readonly as this set becomes a DFA state. */ - public ?Map $configLookup = null; + // ConfigHashSet : Dictionary + public ?ConfigHashSet $configLookup = null; /** * Track the elements as they are added to the set; supports get(i). @@ -83,32 +84,7 @@ public function __construct(bool $fullCtx = true) * not including context. Wiped out when we go readonly as this se * becomes a DFA state. */ - $this->configLookup = new Map(new class implements Equivalence { - public function equivalent(Hashable $left, Hashable $right): bool - { - if ($left === $right) { - return true; - } - - if ($left === null || $right === null) return false; - return true; - } - - public function hash(Hashable $value): int - { - if (!($value instanceof ATNConfig)) return 0; - return Hasher::hash( - $value->state->stateNumber, - $value->alt, - $value->semanticContext, - ); - } - - public function equals(object $other): bool - { - return $other instanceof self; - } - }); + $this->configLookup = new ConfigHashSet(); $this->fullCtx = $fullCtx; } @@ -306,6 +282,8 @@ public function contains(object $item): bool throw new \InvalidArgumentException('This method is not implemented for readonly sets.'); } + if (!($item instanceof ATNConfig)) return false; + return $this->configLookup->contains($item); } diff --git a/src/Atn/ConfigHashSet.php b/src/Atn/ConfigHashSet.php new file mode 100644 index 0000000..54e92e6 --- /dev/null +++ b/src/Atn/ConfigHashSet.php @@ -0,0 +1,80 @@ +state->stateNumber === $right->state->stateNumber + && $left->alt === $right->alt + && $left->semanticContext->equals($right->semanticContext); + } + + public function hash(Hashable $value): int + { + if (!($value instanceof ATNConfig)) return 0; + return Hasher::hash( + $value->state->stateNumber, + $value->alt, + $value->semanticContext, + ); + } + + public function equals(object $other): bool + { + return $other instanceof self; + } + }); + else + parent::__construct($comparer); + } + + public function getOrAdd(ATNConfig $config): ATNConfig + { + $existing = null; + if ($this->tryGetValue($config, $existing)) + return $existing; + else + { + $this->put($config, $config); + return $config; + } + } +} diff --git a/src/Atn/OrderedATNConfigSet.php b/src/Atn/OrderedATNConfigSet.php index 42fdaa9..3ecfe73 100644 --- a/src/Atn/OrderedATNConfigSet.php +++ b/src/Atn/OrderedATNConfigSet.php @@ -15,13 +15,18 @@ public function __construct() { parent::__construct(); - $this->configLookup = new Map(new class implements Equivalence { + $this->configLookup = new ConfigHashSet(new class implements Equivalence { public function equivalent(Hashable $left, Hashable $right): bool { if ($left === $right) { return true; } - if ($left === null || $right === null) return false; + + /** @phpstan-ignore-next-line */ + if ($left == null) return false; + /** @phpstan-ignore-next-line */ + if ($right == null) return false; + return $left->equals($right); } diff --git a/src/PredictionContexts/PredictionContext.php b/src/PredictionContexts/PredictionContext.php index 0ba9084..be4fd07 100644 --- a/src/PredictionContexts/PredictionContext.php +++ b/src/PredictionContexts/PredictionContext.php @@ -427,13 +427,17 @@ public static function mergeArrays( } // merge sorted payloads a + b => M + /** @var int $i */ $i = 0;// walks a + /** @var int $j */ $j = 0;// walks b + /** @var int $k */ $k = 0;// walks target M array - $mergedReturnStates = []; + /** @var int[] $mergedReturnStates */ + $mergedReturnStates = []; for ($ini = 0; $ini < \count($a->returnStates) + \count($b->returnStates); $ini++) { - $mergedReturnStates[$ini] = null; + $mergedReturnStates[$ini] = 0; } $mergedParents = []; for ($ini = 0; $ini < \count($a->returnStates) + \count($b->returnStates); $ini++) { @@ -585,6 +589,7 @@ protected static function combineCommonParents(array &$parents): void $uniqueParents = new Map(); foreach ($parents as $parent) { + /** @phpstan-ignore-next-line */ if ($parent != null && !$uniqueParents->contains($parent)) { // don't replace. $uniqueParents->put($parent, $parent); @@ -592,6 +597,7 @@ protected static function combineCommonParents(array &$parents): void } foreach ($parents as $i => $parent) { + /** @phpstan-ignore-next-line */ if ($parent != null) $parents[$i] = $uniqueParents->get($parent); } diff --git a/src/Utils/Map.php b/src/Utils/Map.php index dbeeb06..bfa0e61 100644 --- a/src/Utils/Map.php +++ b/src/Utils/Map.php @@ -13,7 +13,7 @@ * @template K of Hashable * @template V */ -final class Map implements Equatable, \Countable, \IteratorAggregate +class Map implements Equatable, \Countable, \IteratorAggregate { /** @var array> */ private array $table = []; @@ -136,34 +136,7 @@ public function remove(Hashable $key): void public function equals(object $other): bool { - if ($this === $other) { - return true; - } - - if (!$other instanceof self - || $this->size !== $other->size - || !$this->equivalence->equals($other->equivalence)) { - return false; - } - - foreach ($this->table as $hash => $bucket) { - if (!isset($other->table[$hash]) || \count($bucket) !== \count($other->table[$hash])) { - return false; - } - - $otherBucket = $other->table[$hash]; - - foreach ($bucket as $index => [$key, $value]) { - [$otherKey, $otherValue] = $otherBucket[$index]; - - if (!$this->equivalence->equivalent($key, $otherKey) - || !self::isEqual($value, $otherValue)) { - return false; - } - } - } - - return true; + return false; } /** @@ -216,10 +189,15 @@ private static function isEqual(mixed $left, mixed $right): bool return $left === $right; } - public function getOrAdd(Hashable $value): Hashable + + /** + * @param K $key + * @param V $value + * @return bool + */ + public function tryGetValue(Hashable $key, mixed &$value): bool { - $key = $value; - $hash = $this->equivalence->hash($value); + $hash = $this->equivalence->hash($key); if (!isset($this->table[$hash])) { $this->table[$hash] = []; @@ -227,21 +205,11 @@ public function getOrAdd(Hashable $value): Hashable foreach ($this->table[$hash] as $index => [$entryKey, $entryValue]) { if ($this->equivalence->equivalent($key, $entryKey)) { - return $entryValue; - } - } - - foreach ($this->table[$hash] as $index => [$entryKey]) { - if ($this->equivalence->equivalent($key, $entryKey)) { - $this->table[$hash][$index] = [$key, $value]; - return $value; + $value = $entryValue; + return true; } } - $this->table[$hash][] = [$key, $value]; - - $this->size++; - - return $value; + return false; } } From 43eddcf13303b8163ac53b01133716d7829eaa90 Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Sat, 24 Dec 2022 15:41:51 -0500 Subject: [PATCH 09/18] Clean up assorted phpstand and phpcs errors. --- src/Atn/ATNConfigSet.php | 7 +- src/Atn/ConfigHashSet.php | 104 +++++++++---------- src/Atn/OrderedATNConfigSet.php | 13 ++- src/PredictionContexts/PredictionContext.php | 50 ++++----- src/Utils/Map.php | 11 +- 5 files changed, 95 insertions(+), 90 deletions(-) diff --git a/src/Atn/ATNConfigSet.php b/src/Atn/ATNConfigSet.php index 9710d7f..61149f3 100644 --- a/src/Atn/ATNConfigSet.php +++ b/src/Atn/ATNConfigSet.php @@ -6,14 +6,13 @@ use Antlr\Antlr4\Runtime\Atn\SemanticContexts\SemanticContext; use Antlr\Antlr4\Runtime\Comparison\Equality; -use Antlr\Antlr4\Runtime\Comparison\Equivalence; use Antlr\Antlr4\Runtime\Comparison\Hashable; use Antlr\Antlr4\Runtime\Comparison\Hasher; use Antlr\Antlr4\Runtime\PredictionContexts\PredictionContext; use Antlr\Antlr4\Runtime\Utils\BitSet; use Antlr\Antlr4\Runtime\Utils\DoubleKeyMap; -use Antlr\Antlr4\Runtime\Utils\Set; use Antlr\Antlr4\Runtime\Utils\Map; +use Antlr\Antlr4\Runtime\Utils\Set; /** * Specialized {@see Set} of `{@see ATNConfig}`s that can track info @@ -282,7 +281,9 @@ public function contains(object $item): bool throw new \InvalidArgumentException('This method is not implemented for readonly sets.'); } - if (!($item instanceof ATNConfig)) return false; + if (!($item instanceof ATNConfig)) { + return false; + } return $this->configLookup->contains($item); } diff --git a/src/Atn/ConfigHashSet.php b/src/Atn/ConfigHashSet.php index 54e92e6..9200ec9 100644 --- a/src/Atn/ConfigHashSet.php +++ b/src/Atn/ConfigHashSet.php @@ -4,15 +4,9 @@ namespace Antlr\Antlr4\Runtime\Atn; -use Antlr\Antlr4\Runtime\Atn\SemanticContexts\SemanticContext; -use Antlr\Antlr4\Runtime\Comparison\Equality; use Antlr\Antlr4\Runtime\Comparison\Equivalence; use Antlr\Antlr4\Runtime\Comparison\Hashable; use Antlr\Antlr4\Runtime\Comparison\Hasher; -use Antlr\Antlr4\Runtime\PredictionContexts\PredictionContext; -use Antlr\Antlr4\Runtime\Utils\BitSet; -use Antlr\Antlr4\Runtime\Utils\DoubleKeyMap; -use Antlr\Antlr4\Runtime\Utils\Set; use Antlr\Antlr4\Runtime\Utils\Map; /** @@ -24,57 +18,61 @@ */ final class ConfigHashSet extends Map { - public function __construct(Equivalence $comparer = null) - { - if ($comparer === null) - parent::__construct(new class implements Equivalence { - public function equivalent(Hashable $left, Hashable $right): bool - { - if ($left === $right) { - return true; - } + public function __construct(?Equivalence $comparer = null) + { + if ($comparer === null) { + parent::__construct(new class implements Equivalence { + public function equivalent(Hashable $left, Hashable $right): bool + { + if (! $left instanceof ATNConfig) { + return false; + } - /** @phpstan-ignore-next-line */ - if ($left == null) return false; - /** @phpstan-ignore-next-line */ - if ($right == null) return false; + if (! $right instanceof ATNConfig) { + return false; + } - if (! $left instanceof ATNConfig) return false; - if (! $right instanceof ATNConfig) return false; + if ($left === $right) { + return true; + } - return $left->state->stateNumber === $right->state->stateNumber - && $left->alt === $right->alt - && $left->semanticContext->equals($right->semanticContext); - } + return $left->state->stateNumber === $right->state->stateNumber + && $left->alt === $right->alt + && $left->semanticContext->equals($right->semanticContext); + } - public function hash(Hashable $value): int - { - if (!($value instanceof ATNConfig)) return 0; - return Hasher::hash( - $value->state->stateNumber, - $value->alt, - $value->semanticContext, - ); - } + public function hash(Hashable $value): int + { + if (!($value instanceof ATNConfig)) { + return 0; + } - public function equals(object $other): bool - { - return $other instanceof self; - } - }); - else - parent::__construct($comparer); - } + return Hasher::hash( + $value->state->stateNumber, + $value->alt, + $value->semanticContext, + ); + } - public function getOrAdd(ATNConfig $config): ATNConfig - { - $existing = null; - if ($this->tryGetValue($config, $existing)) - return $existing; - else - { - $this->put($config, $config); - return $config; - } - } + public function equals(object $other): bool + { + return $other instanceof self; + } + }); + } else { + parent::__construct($comparer); + } + } + + public function getOrAdd(ATNConfig $config): ATNConfig + { + $existing = null; + if ($this->tryGetValue($config, $existing)) { + return $existing; + } else { + $this->put($config, $config); + + return $config; + } + } } diff --git a/src/Atn/OrderedATNConfigSet.php b/src/Atn/OrderedATNConfigSet.php index 3ecfe73..e1da41d 100644 --- a/src/Atn/OrderedATNConfigSet.php +++ b/src/Atn/OrderedATNConfigSet.php @@ -3,11 +3,9 @@ declare(strict_types=1); namespace Antlr\Antlr4\Runtime\Atn; -use Antlr\Antlr4\Runtime\Comparison\Equality; + use Antlr\Antlr4\Runtime\Comparison\Equivalence; use Antlr\Antlr4\Runtime\Comparison\Hashable; -use Antlr\Antlr4\Runtime\Comparison\Hasher; -use Antlr\Antlr4\Runtime\Utils\Map; final class OrderedATNConfigSet extends ATNConfigSet { @@ -23,9 +21,14 @@ public function equivalent(Hashable $left, Hashable $right): bool } /** @phpstan-ignore-next-line */ - if ($left == null) return false; + if ($left === null) { + return false; + } + /** @phpstan-ignore-next-line */ - if ($right == null) return false; + if ($right === null) { + return false; + } return $left->equals($right); } diff --git a/src/PredictionContexts/PredictionContext.php b/src/PredictionContexts/PredictionContext.php index be4fd07..68f0f95 100644 --- a/src/PredictionContexts/PredictionContext.php +++ b/src/PredictionContexts/PredictionContext.php @@ -294,8 +294,7 @@ public static function mergeSingletons( $payloads[0] = $b->returnState; $payloads[1] = $a->returnState; $parents = [$b->parent, $a->parent]; - } - else { + } else { $payloads[0] = $a->returnState; $payloads[1] = $b->returnState; $parents = [$a->parent, $b->parent]; @@ -427,15 +426,15 @@ public static function mergeArrays( } // merge sorted payloads a + b => M - /** @var int $i */ + /** @var int $i */ $i = 0;// walks a - /** @var int $j */ + /** @var int $j */ $j = 0;// walks b - /** @var int $k */ + /** @var int $k */ $k = 0;// walks target M array - /** @var int[] $mergedReturnStates */ - $mergedReturnStates = []; + /** @var array $mergedReturnStates */ + $mergedReturnStates = []; for ($ini = 0; $ini < \count($a->returnStates) + \count($b->returnStates); $ini++) { $mergedReturnStates[$ini] = 0; } @@ -491,18 +490,18 @@ public static function mergeArrays( } // copy over any payloads remaining in either array - if ($i < \count($a->returnStates)) - { - for ($p = $i; $p < \count($a->returnStates); $p++) - { + if ($i < \count($a->returnStates)) { + /** @var int $p */ + $p = $j; + for (; $p < \count($b->returnStates); $p++) { $mergedParents[$k] = $a->parents[$p]; $mergedReturnStates[$k] = $a->returnStates[$p]; $k++; } - } - else { - for ($p = $j; $p < \count($b->returnStates); $p++) - { + } else { + /** @var int $p */ + $p = $j; + for (; $p < \count($b->returnStates); $p++) { $mergedParents[$k] = $b->parents[$p]; $mergedReturnStates[$k] = $b->returnStates[$p]; $k++; @@ -512,11 +511,13 @@ public static function mergeArrays( // trim merged if we combined a few that had same stack tops if ($k < \count($mergedParents)) { // write index < last position; trim - if ($k === 1) - { // for just one merged element, return singleton top + if ($k === 1) { + // for just one merged element, return singleton top $a_ = SingletonPredictionContext::create($mergedParents[0], $mergedReturnStates[0]); - if ($mergeCache !== null) $mergeCache->set($a, $b, $a_); + if ($mergeCache !== null) { + $mergeCache->set($a, $b, $a_); + } return $a_; } @@ -527,7 +528,6 @@ public static function mergeArrays( $mergedReturnStates = \array_slice($mergedReturnStates, 0, $k); } - $M = new ArrayPredictionContext($mergedParents, $mergedReturnStates); // if we created same array as a or b, return that instead @@ -578,28 +578,30 @@ public static function mergeArrays( if ($mergeCache !== null) { $mergeCache->set($a, $b, $M); } + return $M; } /** - * @param array $parents + * @param array $parents */ protected static function combineCommonParents(array &$parents): void { + /** @var Map $uniqueParents */ $uniqueParents = new Map(); + /** @var PredictionContext|null $parent */ foreach ($parents as $parent) { - /** @phpstan-ignore-next-line */ - if ($parent != null && !$uniqueParents->contains($parent)) { + if ($parent !== null && !$uniqueParents->contains($parent)) { // don't replace. $uniqueParents->put($parent, $parent); } } foreach ($parents as $i => $parent) { - /** @phpstan-ignore-next-line */ - if ($parent != null) + if ($parent !== null) { $parents[$i] = $uniqueParents->get($parent); + } } } diff --git a/src/Utils/Map.php b/src/Utils/Map.php index bfa0e61..f89afec 100644 --- a/src/Utils/Map.php +++ b/src/Utils/Map.php @@ -32,6 +32,7 @@ public function isEmpty(): bool { return $this->count() === 0; } + public function count(): int { return $this->size; @@ -136,7 +137,7 @@ public function remove(Hashable $key): void public function equals(object $other): bool { - return false; + return false; } /** @@ -189,11 +190,10 @@ private static function isEqual(mixed $left, mixed $right): bool return $left === $right; } - + /** * @param K $key * @param V $value - * @return bool */ public function tryGetValue(Hashable $key, mixed &$value): bool { @@ -206,10 +206,11 @@ public function tryGetValue(Hashable $key, mixed &$value): bool foreach ($this->table[$hash] as $index => [$entryKey, $entryValue]) { if ($this->equivalence->equivalent($key, $entryKey)) { $value = $entryValue; - return true; + + return true; } } - return false; + return false; } } From 15d156c9dc8fb8a2a67cf9d010c46b3ad80c01ab Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Sun, 25 Dec 2022 05:26:55 -0500 Subject: [PATCH 10/18] phpstan and phpcs did not catch this obvious static type error. --- src/Atn/ATNConfigSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Atn/ATNConfigSet.php b/src/Atn/ATNConfigSet.php index 61149f3..3034eb6 100644 --- a/src/Atn/ATNConfigSet.php +++ b/src/Atn/ATNConfigSet.php @@ -306,7 +306,7 @@ public function clear(): void $this->configs = []; $this->cachedHashCode = -1; - $this->configLookup = new Map(); + $this->configLookup = new ConfigHashSet(); } public function isReadOnly(): bool From 3b1e1bbff687cd57b75a3e620f81b86e0bc822fa Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Sun, 25 Dec 2022 06:11:01 -0500 Subject: [PATCH 11/18] Remove phpcs error, add in more type checking. --- src/Atn/ATNConfigSet.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Atn/ATNConfigSet.php b/src/Atn/ATNConfigSet.php index 3034eb6..02edc75 100644 --- a/src/Atn/ATNConfigSet.php +++ b/src/Atn/ATNConfigSet.php @@ -5,13 +5,13 @@ namespace Antlr\Antlr4\Runtime\Atn; use Antlr\Antlr4\Runtime\Atn\SemanticContexts\SemanticContext; +use Antlr\Antlr4\Runtime\Atn\States\ATNState; use Antlr\Antlr4\Runtime\Comparison\Equality; use Antlr\Antlr4\Runtime\Comparison\Hashable; use Antlr\Antlr4\Runtime\Comparison\Hasher; use Antlr\Antlr4\Runtime\PredictionContexts\PredictionContext; use Antlr\Antlr4\Runtime\Utils\BitSet; use Antlr\Antlr4\Runtime\Utils\DoubleKeyMap; -use Antlr\Antlr4\Runtime\Utils\Map; use Antlr\Antlr4\Runtime\Utils\Set; /** @@ -125,12 +125,14 @@ public function add(ATNConfig $config, ?DoubleKeyMap $mergeCache = null): bool } // A previous (s,i,pi,_), merge with it and save result + /** @var bool $rootIsWildcard */ $rootIsWildcard = !$this->fullCtx; if ($existing->context === null || $config->context === null) { throw new \LogicException('Unexpected null context.'); } + /** @var PredictionContext $merged */ $merged = PredictionContext::merge($existing->context, $config->context, $rootIsWildcard, $mergeCache); // No need to check for existing->context, config->context in cache @@ -162,8 +164,12 @@ public function elements(): array return $this->configs; } + /** + * @return Set + */ public function getStates(): Set { + /** @var Set $states */ $states = new Set(); foreach ($this->configs as $config) { $states->add($config->state); From 6555e81b98ad254c0fddf13948017c8b85b276d1 Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Sun, 25 Dec 2022 06:30:00 -0500 Subject: [PATCH 12/18] Add in more type safety with phpstan and phpcs. Note, getOrAdd() always returns an ATNConfig not null. --- src/Atn/ConfigHashSet.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Atn/ConfigHashSet.php b/src/Atn/ConfigHashSet.php index 9200ec9..5c3c47f 100644 --- a/src/Atn/ConfigHashSet.php +++ b/src/Atn/ConfigHashSet.php @@ -15,6 +15,8 @@ * {@code (s,i,_,semctx)} to be equal. Unfortunately, this key effectively doubles * the number of objects associated with ATNConfigs. The other solution is to * use a hash table that lets us specify the equals/hashcode operation. + * + * @extends Map */ final class ConfigHashSet extends Map { @@ -66,6 +68,7 @@ public function equals(object $other): bool public function getOrAdd(ATNConfig $config): ATNConfig { + /** @var ?ATNConfig $existing */ $existing = null; if ($this->tryGetValue($config, $existing)) { return $existing; From 477a44f3db9d976c334ff72d4ce2a4bfeda4bca4 Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Sun, 25 Dec 2022 09:02:04 -0500 Subject: [PATCH 13/18] Fundtion equivalent() should call equivalent() of child data structures, not Equality methods. --- src/Atn/OrderedATNConfigSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Atn/OrderedATNConfigSet.php b/src/Atn/OrderedATNConfigSet.php index e1da41d..b688221 100644 --- a/src/Atn/OrderedATNConfigSet.php +++ b/src/Atn/OrderedATNConfigSet.php @@ -30,7 +30,7 @@ public function equivalent(Hashable $left, Hashable $right): bool return false; } - return $left->equals($right); + return $left->equivalent($right); } public function hash(Hashable $value): int From 62ed357b5114423b88ac446cb3d9bd3aabb2c273 Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Sun, 25 Dec 2022 09:03:10 -0500 Subject: [PATCH 14/18] Copying regressions. --- src/PredictionContexts/PredictionContext.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PredictionContexts/PredictionContext.php b/src/PredictionContexts/PredictionContext.php index 68f0f95..3f0cd0a 100644 --- a/src/PredictionContexts/PredictionContext.php +++ b/src/PredictionContexts/PredictionContext.php @@ -492,8 +492,8 @@ public static function mergeArrays( // copy over any payloads remaining in either array if ($i < \count($a->returnStates)) { /** @var int $p */ - $p = $j; - for (; $p < \count($b->returnStates); $p++) { + $p = $i; + for (; $p < \count($a->returnStates); $p++) { $mergedParents[$k] = $a->parents[$p]; $mergedReturnStates[$k] = $a->returnStates[$p]; $k++; From 3000a374e80b6fa811ecea1d6fbadf121406ed87 Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Sun, 25 Dec 2022 09:14:46 -0500 Subject: [PATCH 15/18] Simplify --- src/Atn/ConfigHashSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Atn/ConfigHashSet.php b/src/Atn/ConfigHashSet.php index 5c3c47f..9806a16 100644 --- a/src/Atn/ConfigHashSet.php +++ b/src/Atn/ConfigHashSet.php @@ -45,7 +45,7 @@ public function equivalent(Hashable $left, Hashable $right): bool public function hash(Hashable $value): int { - if (!($value instanceof ATNConfig)) { + if (! $value instanceof ATNConfig) { return 0; } From 583d722e44db7b9d738bea63cb7e1cdb1ddaad61 Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Sun, 25 Dec 2022 09:15:11 -0500 Subject: [PATCH 16/18] There is no equivalent() is Hashable. --- src/Atn/OrderedATNConfigSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Atn/OrderedATNConfigSet.php b/src/Atn/OrderedATNConfigSet.php index b688221..e1da41d 100644 --- a/src/Atn/OrderedATNConfigSet.php +++ b/src/Atn/OrderedATNConfigSet.php @@ -30,7 +30,7 @@ public function equivalent(Hashable $left, Hashable $right): bool return false; } - return $left->equivalent($right); + return $left->equals($right); } public function hash(Hashable $value): int From 947247d583b0b839a6773f698e629b092fbd967c Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Thu, 29 Dec 2022 04:08:38 -0500 Subject: [PATCH 17/18] Default value for forcing hash computation is "null" in PHP target, not "-1" as in the other targets. --- src/Atn/ATNConfigSet.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Atn/ATNConfigSet.php b/src/Atn/ATNConfigSet.php index 02edc75..c9ed1c1 100644 --- a/src/Atn/ATNConfigSet.php +++ b/src/Atn/ATNConfigSet.php @@ -311,7 +311,7 @@ public function clear(): void } $this->configs = []; - $this->cachedHashCode = -1; + $this->cachedHashCode = null; $this->configLookup = new ConfigHashSet(); } From c2551b43ffe032515a70a12de0133fb4a3ee8a0d Mon Sep 17 00:00:00 2001 From: Ken Domino Date: Fri, 30 Dec 2022 08:00:40 -0500 Subject: [PATCH 18/18] Changes for #36 --- src/Atn/ATNConfig.php | 2 +- src/Atn/ATNSimulator.php | 3 +- src/Atn/LexerATNConfig.php | 6 +-- src/Atn/States/ATNState.php | 2 +- src/PredictionContexts/IdentityHashMap.php | 47 ++++++++++++++++++++ src/PredictionContexts/PredictionContext.php | 15 +++---- 6 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 src/PredictionContexts/IdentityHashMap.php diff --git a/src/Atn/ATNConfig.php b/src/Atn/ATNConfig.php index 92b8894..bfd45aa 100644 --- a/src/Atn/ATNConfig.php +++ b/src/Atn/ATNConfig.php @@ -128,10 +128,10 @@ public function equals(object $other): bool } return $other instanceof self + && $this->state->stateNumber === $other->state->stateNumber && $this->alt === $other->alt && $this->isPrecedenceFilterSuppressed() === $other->isPrecedenceFilterSuppressed() && $this->semanticContext->equals($other->semanticContext) - && Equality::equals($this->state, $other->state) && Equality::equals($this->context, $other->context); } diff --git a/src/Atn/ATNSimulator.php b/src/Atn/ATNSimulator.php index 5ecce88..2aae6f9 100644 --- a/src/Atn/ATNSimulator.php +++ b/src/Atn/ATNSimulator.php @@ -5,6 +5,7 @@ namespace Antlr\Antlr4\Runtime\Atn; use Antlr\Antlr4\Runtime\Dfa\DFAState; +use Antlr\Antlr4\Runtime\PredictionContexts\IdentityHashMap; use Antlr\Antlr4\Runtime\PredictionContexts\PredictionContext; use Antlr\Antlr4\Runtime\PredictionContexts\PredictionContextCache; @@ -92,7 +93,7 @@ public function getSharedContextCache(): PredictionContextCache public function getCachedContext(PredictionContext $context): PredictionContext { - $visited = []; + $visited = new IdentityHashMap(); return PredictionContext::getCachedPredictionContext( $context, diff --git a/src/Atn/LexerATNConfig.php b/src/Atn/LexerATNConfig.php index 0b353c1..d021e3d 100644 --- a/src/Atn/LexerATNConfig.php +++ b/src/Atn/LexerATNConfig.php @@ -63,15 +63,15 @@ public function equals(object $other): bool return false; } - if (!parent::equals($other)) { + if ($this->passedThroughNonGreedyDecision !== $other->passedThroughNonGreedyDecision) { return false; } - if ($this->passedThroughNonGreedyDecision !== $other->passedThroughNonGreedyDecision) { + if (!Equality::equals($this->lexerActionExecutor, $other->lexerActionExecutor)) { return false; } - return Equality::equals($this->lexerActionExecutor, $other->lexerActionExecutor); + return parent::equals($other); } private static function checkNonGreedyDecision(LexerATNConfig $source, ATNState $target): bool diff --git a/src/Atn/States/ATNState.php b/src/Atn/States/ATNState.php index 8a9d51e..3261754 100644 --- a/src/Atn/States/ATNState.php +++ b/src/Atn/States/ATNState.php @@ -152,7 +152,7 @@ public function __toString(): string public function hashCode(): int { - return $this->getStateType(); + return $this->stateNumber; } abstract public function getStateType(): int; diff --git a/src/PredictionContexts/IdentityHashMap.php b/src/PredictionContexts/IdentityHashMap.php new file mode 100644 index 0000000..e3be649 --- /dev/null +++ b/src/PredictionContexts/IdentityHashMap.php @@ -0,0 +1,47 @@ + + */ +class IdentityHashMap extends Map +{ + public function __construct() + { + parent::__construct(new class implements Equivalence { + public function equivalent(Hashable $left, Hashable $right): bool + { + if (! $left instanceof PredictionContext) { + return false; + } + + if (! $right instanceof PredictionContext) { + return false; + } + + return $left === $right; + } + + public function hash(Hashable $value): int + { + if (! $value instanceof PredictionContext) { + return 0; + } + + return $value->hashCode(); + } + + public function equals(object $other): bool + { + return $other instanceof self; + } + }); + } +} diff --git a/src/PredictionContexts/PredictionContext.php b/src/PredictionContexts/PredictionContext.php index 3f0cd0a..e19a400 100644 --- a/src/PredictionContexts/PredictionContext.php +++ b/src/PredictionContexts/PredictionContext.php @@ -605,19 +605,16 @@ protected static function combineCommonParents(array &$parents): void } } - /** - * @param array $visited - */ public static function getCachedPredictionContext( PredictionContext $context, PredictionContextCache $contextCache, - array &$visited, + IdentityHashMap &$visited, ): self { if ($context->isEmpty()) { return $context; } - $existing = $visited[\spl_object_id($context)] ?? null; + $existing = $visited->get($context); if ($existing !== null) { return $existing; @@ -626,7 +623,7 @@ public static function getCachedPredictionContext( $existing = $contextCache->get($context); if ($existing !== null) { - $visited[\spl_object_id($context)] = $existing; + $visited->put($context, $existing); return $existing; } @@ -660,7 +657,7 @@ public static function getCachedPredictionContext( if (!$changed) { $contextCache->add($context); - $visited[\spl_object_id($context)] = $context; + $visited->put($context, $context); return $context; } @@ -680,8 +677,8 @@ public static function getCachedPredictionContext( } $contextCache->add($updated); - $visited[\spl_object_id($updated)] = $updated; - $visited[\spl_object_id($context)] = $updated; + $visited->put($updated, $updated); + $visited->put($context, $updated); return $updated; }