Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions src/Atn/ATNConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
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\Comparison\MurmurHash;
use Antlr\Antlr4\Runtime\PredictionContexts\PredictionContext;

/**
Expand Down Expand Up @@ -127,22 +127,25 @@ public function equals(object $other): bool
return true;
}

// Field order follows Java's, cheapest discriminator first. Comparing the
// state by number rather than through `Equality::equals()` also avoids a
// full object comparison on the hottest path in the simulator.
return $other instanceof self
&& $this->state->stateNumber === $other->state->stateNumber
&& $this->alt === $other->alt
&& $this->isPrecedenceFilterSuppressed() === $other->isPrecedenceFilterSuppressed()
&& Equality::equals($this->context, $other->context)
&& $this->semanticContext->equals($other->semanticContext)
&& Equality::equals($this->state, $other->state)
&& Equality::equals($this->context, $other->context);
&& $this->isPrecedenceFilterSuppressed() === $other->isPrecedenceFilterSuppressed();
}

public function hashCode(): int
{
return Hasher::hash(
return MurmurHash::hash([
$this->state->stateNumber,
$this->alt,
$this->context,
$this->semanticContext,
);
], 7);
}

public function toString(bool $showAlt): string
Expand Down Expand Up @@ -172,15 +175,10 @@ public function toString(bool $showAlt): string

public function __toString(): string
{
return \sprintf(
'(%s,%d%s%s%s)',
$this->state,
$this->alt,
$this->context !== null ? ',[' . $this->context . ']' : '',
$this->semanticContext->equals(SemanticContext::none())
? ''
: ',' . $this->semanticContext,
$this->reachesIntoOuterContext > 0 ? ',up=' . $this->reachesIntoOuterContext : '',
);
// Java's no-arg `toString()` is `toString(null, true)`. Duplicating the
// formatting here is what let the two drift: this copy printed the raw
// `reachesIntoOuterContext` field, which carries the
// SUPPRESS_PRECEDENCE_FILTER bit, instead of `getOuterContextDepth()`.
return $this->toString(true);
}
}
94 changes: 54 additions & 40 deletions src/Atn/ATNConfigSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
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\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;
Expand All @@ -33,6 +32,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.
*
* @var Set<ATNConfig>|null
*/
public ?Set $configLookup = null;

Expand Down Expand Up @@ -82,32 +83,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 Set(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);
}

public function hash(Hashable $value): int
{
return $value->hashCode();
}

public function equals(object $other): bool
{
return $other instanceof self;
}
});
$this->configLookup = new Set(new ConfigEquivalence());

$this->fullCtx = $fullCtx;
}
Expand All @@ -121,6 +97,8 @@ public function equals(object $other): bool
* This method updates {@see ATNConfigSet::$dipsIntoOuterContext} and
* {@see ATNConfigSet::$hasSemanticContext} when necessary.
*
* @param DoubleKeyMap<PredictionContext, PredictionContext, PredictionContext>|null $mergeCache
*
* @throws \InvalidArgumentException
*/
public function add(ATNConfig $config, ?DoubleKeyMap $mergeCache = null): bool
Expand All @@ -140,7 +118,10 @@ public function add(ATNConfig $config, ?DoubleKeyMap $mergeCache = null): bool
/** @var ATNConfig $existing */
$existing = $this->configLookup->getOrAdd($config);

if ($existing->equals($config)) {
// Identity, not equality: `getOrAdd` returns the argument only when it was
// genuinely new. Comparing with `equals()` would also take this branch for
// a distinct-but-equal configuration and append a duplicate to `$configs`.
if ($existing === $config) {
$this->cachedHashCode = null;

$this->configs[] = $config; // track order here
Expand Down Expand Up @@ -186,8 +167,12 @@ public function elements(): array
return $this->configs;
}

/**
* @return Set<ATNState>
*/
public function getStates(): Set
{
/** @var Set<ATNState> $states */
$states = new Set();
foreach ($this->configs as $config) {
$states->add($config->state);
Expand Down Expand Up @@ -268,25 +253,43 @@ public function equals(object $other): bool
return false;
}

return $this->fullCtx === $other->fullCtx
// Field order and comparison kinds follow Java. In particular
// `conflictingAlts` is compared by **reference** there
// (`this.conflictingAlts == other.conflictingAlts`), not by value:
// comparing it by value merged config sets that Java keeps distinct.
return Equality::equals($this->configs, $other->configs)
&& $this->fullCtx === $other->fullCtx
&& $this->uniqueAlt === $other->uniqueAlt
&& $this->conflictingAlts === $other->conflictingAlts
&& $this->hasSemanticContext === $other->hasSemanticContext
&& $this->dipsIntoOuterContext === $other->dipsIntoOuterContext
&& Equality::equals($this->configs, $other->configs)
&& Equality::equals($this->conflictingAlts, $other->conflictingAlts);
&& $this->dipsIntoOuterContext === $other->dipsIntoOuterContext;
}

public function hashCode(): int
{
// Only a read-only set may cache: while the set is still mutable its
// configurations keep having their contexts merged underneath it.
if (!$this->isReadOnly()) {
return Hasher::hash($this->configs);
return $this->computeHashCode();
}

if ($this->cachedHashCode === null) {
$this->cachedHashCode = Hasher::hash($this->configs);
return $this->cachedHashCode ??= $this->computeHashCode();
}

/**
* Java's `ATNConfigSet.hashCode()` is `configs.hashCode()` — that is,
* `AbstractList.hashCode()`: a 31-based accumulation over the elements,
* wrapping at 32 bits.
*/
private function computeHashCode(): int
{
$hash = 1;

foreach ($this->configs as $config) {
$hash = (31 * $hash + $config->hashCode()) & 0xFFFFFFFF;
}

return $this->cachedHashCode;
return $hash >= 0x80000000 ? $hash - 0x100000000 : $hash;
}

public function getLength(): int
Expand All @@ -313,6 +316,9 @@ public function containsFast(ATNConfig $item): bool
return $this->contains($item);
}

/**
* @return \Iterator<int, ATNConfig>
*/
public function getIterator(): \Iterator
{
return new \ArrayIterator($this->configs);
Expand All @@ -325,8 +331,14 @@ public function clear(): void
}

$this->configs = [];
$this->cachedHashCode = -1;
$this->configLookup = new Set();
// `null` is the "not computed" sentinel; `-1` was a valid cached hash and
// pinned every cleared set to the same value. (Java's sentinel *is* -1,
// which is why the port picked it up.)
$this->cachedHashCode = null;
// Clear in place rather than replacing the set: a fresh `Set` would fall
// back to the default equivalence and silently drop `ConfigEquivalence`
// (or, in `OrderedATNConfigSet`, its own), disabling context merging.
$this->configLookup?->clear();
}

public function isReadOnly(): bool
Expand Down Expand Up @@ -358,7 +370,9 @@ public function __toString(): string
return \sprintf(
'[%s]%s%s%s%s',
\implode(', ', $this->configs),
$this->hasSemanticContext ? ',hasSemanticContext=' . $this->hasSemanticContext : '',
// Java appends the boolean itself, which prints `true`; interpolating
// a PHP bool printed `1`.
$this->hasSemanticContext ? ',hasSemanticContext=true' : '',
$this->uniqueAlt !== ATN::INVALID_ALT_NUMBER ? ',uniqueAlt=' . $this->uniqueAlt : '',
$this->conflictingAlts !== null ? ',conflictingAlts=' . $this->conflictingAlts : '',
$this->dipsIntoOuterContext ? ',dipsIntoOuterContext' : '',
Expand Down
12 changes: 6 additions & 6 deletions src/Atn/ATNDeserializationOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ final class ATNDeserializationOptions

private bool $generateRuleBypassTransitions;

private static ?self $defaultOptions = null;

public static function defaultOptions(): ATNDeserializationOptions
{
static $defaultOptions;

if ($defaultOptions === null) {
$defaultOptions = new ATNDeserializationOptions();
$defaultOptions->readOnly = true;
if (self::$defaultOptions === null) {
self::$defaultOptions = new ATNDeserializationOptions();
self::$defaultOptions->readOnly = true;
}

return $defaultOptions;
return self::$defaultOptions;
}

public function __construct(?ATNDeserializationOptions $options = null)
Expand Down
9 changes: 5 additions & 4 deletions src/Atn/ATNSimulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ public function __construct(ATN $atn, PredictionContextCache $sharedContextCache
$this->sharedContextCache = $sharedContextCache;
}

private static ?DFAState $error = null;

public static function error(): DFAState
{
static $error;

return $error ?? ($error = new DFAState(new ATNConfigSet(), 0x7FFFFFFF));
return self::$error ??= new DFAState(new ATNConfigSet(), 0x7FFFFFFF);
}

abstract public function reset(): void;
Expand All @@ -92,7 +92,8 @@ public function getSharedContextCache(): PredictionContextCache

public function getCachedContext(PredictionContext $context): PredictionContext
{
$visited = [];
/** @var \SplObjectStorage<PredictionContext, PredictionContext> $visited */
$visited = new \SplObjectStorage();

return PredictionContext::getCachedPredictionContext(
$context,
Expand Down
2 changes: 1 addition & 1 deletion src/Atn/Actions/LexerAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*
* @author Sam Harwell
*/
interface LexerAction extends Hashable
interface LexerAction extends Hashable, \Stringable
{
/**
* Gets the serialization type of the lexer action.
Expand Down
4 changes: 2 additions & 2 deletions src/Atn/Actions/LexerChannelAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Antlr\Antlr4\Runtime\Atn\Actions;

use Antlr\Antlr4\Runtime\Comparison\Hasher;
use Antlr\Antlr4\Runtime\Comparison\MurmurHash;
use Antlr\Antlr4\Runtime\Lexer;

/**
Expand Down Expand Up @@ -70,7 +70,7 @@ public function execute(Lexer $lexer): void

public function hashCode(): int
{
return Hasher::hash($this->getActionType(), $this->channel);
return MurmurHash::hash([$this->getActionType(), $this->channel]);
}

public function equals(object $other): bool
Expand Down
9 changes: 7 additions & 2 deletions src/Atn/Actions/LexerCustomAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Antlr\Antlr4\Runtime\Atn\Actions;

use Antlr\Antlr4\Runtime\Comparison\Hasher;
use Antlr\Antlr4\Runtime\Comparison\MurmurHash;
use Antlr\Antlr4\Runtime\Lexer;

/**
Expand Down Expand Up @@ -97,9 +97,14 @@ public function execute(Lexer $lexer): void
$lexer->action(null, $this->ruleIndex, $this->actionIndex);
}

public function __toString(): string
{
return \sprintf('custom(%d:%d)', $this->ruleIndex, $this->actionIndex);
}

public function hashCode(): int
{
return Hasher::hash($this->getActionType(), $this->ruleIndex, $this->actionIndex);
return MurmurHash::hash([$this->getActionType(), $this->ruleIndex, $this->actionIndex]);
}

public function equals(object $other): bool
Expand Down
9 changes: 7 additions & 2 deletions src/Atn/Actions/LexerIndexedCustomAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Antlr\Antlr4\Runtime\Atn\Actions;

use Antlr\Antlr4\Runtime\Comparison\Hasher;
use Antlr\Antlr4\Runtime\Comparison\MurmurHash;
use Antlr\Antlr4\Runtime\Lexer;

/**
Expand Down Expand Up @@ -102,9 +102,14 @@ public function execute(Lexer $lexer): void
$this->action->execute($lexer);
}

public function __toString(): string
{
return \sprintf('%s@%d', $this->action, $this->offset);
}

public function hashCode(): int
{
return Hasher::hash($this->getActionType(), $this->offset, $this->action);
return MurmurHash::hash([$this->offset, $this->action]);
}

public function equals(object $other): bool
Expand Down
4 changes: 2 additions & 2 deletions src/Atn/Actions/LexerModeAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Antlr\Antlr4\Runtime\Atn\Actions;

use Antlr\Antlr4\Runtime\Comparison\Hasher;
use Antlr\Antlr4\Runtime\Comparison\MurmurHash;
use Antlr\Antlr4\Runtime\Lexer;

final class LexerModeAction implements LexerAction
Expand Down Expand Up @@ -64,7 +64,7 @@ public function execute(Lexer $lexer): void

public function hashCode(): int
{
return Hasher::hash($this->getActionType(), $this->mode);
return MurmurHash::hash([$this->getActionType(), $this->mode]);
}

public function equals(object $other): bool
Expand Down
Loading
Loading