diff --git a/composer.json b/composer.json index 66160e1..5160d18 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ ], "require": { "php": "^8.0", - "ext-mbstring": "*" + "ext-mbstring": "^8.0", + "psr/log": "^2.0 || ^3.0" }, "require-dev": { "ergebnis/composer-normalize": "^2.15", diff --git a/composer.lock b/composer.lock index 69e923e..ec737fb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,59 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c9bd0e80e7e22f425eb286b5c42b5f54", - "packages": [], + "content-hash": "6d73951acc3119aabd404dbbe0967053", + "packages": [ + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + } + ], "packages-dev": [ { "name": "dealerdirect/phpcodesniffer-composer-installer", @@ -526,16 +577,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.13.0", + "version": "1.13.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "33aefcdab42900e36366d0feab6206e2dd68f947" + "reference": "aac44118344d197e6d5f7c6cee91885f0a89acdd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/33aefcdab42900e36366d0feab6206e2dd68f947", - "reference": "33aefcdab42900e36366d0feab6206e2dd68f947", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/aac44118344d197e6d5f7c6cee91885f0a89acdd", + "reference": "aac44118344d197e6d5f7c6cee91885f0a89acdd", "shasum": "" }, "require": { @@ -565,22 +616,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.13.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.13.1" }, - "time": "2022-10-21T09:57:39+00:00" + "time": "2022-11-20T08:52:26+00:00" }, { "name": "phpstan/phpstan", - "version": "1.8.11", + "version": "1.9.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "46e223dd68a620da18855c23046ddb00940b4014" + "reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/46e223dd68a620da18855c23046ddb00940b4014", - "reference": "46e223dd68a620da18855c23046ddb00940b4014", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d6fdf01c53978b6429f1393ba4afeca39cc68afa", + "reference": "d6fdf01c53978b6429f1393ba4afeca39cc68afa", "shasum": "" }, "require": { @@ -610,7 +661,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.8.11" + "source": "https://github.com/phpstan/phpstan/tree/1.9.2" }, "funding": [ { @@ -626,7 +677,7 @@ "type": "tidelift" } ], - "time": "2022-10-24T15:45:13+00:00" + "time": "2022-11-10T09:56:11+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -851,7 +902,7 @@ "prefer-lowest": false, "platform": { "php": "^8.0", - "ext-mbstring": "*" + "ext-mbstring": "^8.0" }, "platform-dev": [], "plugin-api-version": "2.3.0" diff --git a/phpcs.xml.dist b/phpcs.xml.dist index bf40ce2..fe0b85e 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -636,6 +636,10 @@ 5 + + src/StdoutMessageLogger.php + + src/Atn/Transitions/AbstractPredicateTransition.php diff --git a/src/Atn/ATNConfig.php b/src/Atn/ATNConfig.php index 425b9b0..7e9c9b9 100644 --- a/src/Atn/ATNConfig.php +++ b/src/Atn/ATNConfig.php @@ -157,7 +157,7 @@ public function toString(bool $showAlt): string $buf .= ',[' . $this->context . ']'; } - if ($this->semanticContext->equals(SemanticContext::none())) { + if (!$this->semanticContext->equals(SemanticContext::none())) { $buf .= ',' . $this->semanticContext; } @@ -177,9 +177,9 @@ public function __toString(): string $this->state, $this->alt, $this->context !== null ? ',[' . $this->context . ']' : '', - $this->semanticContext->equals(SemanticContext::none()) ? - ',' . $this->semanticContext : - '', + $this->semanticContext->equals(SemanticContext::none()) + ? '' + : ',' . $this->semanticContext, $this->reachesIntoOuterContext > 0 ? ',up=' . $this->reachesIntoOuterContext : '', ); } diff --git a/src/Atn/ParserATNSimulator.php b/src/Atn/ParserATNSimulator.php index 36c31a4..57fb55a 100644 --- a/src/Atn/ParserATNSimulator.php +++ b/src/Atn/ParserATNSimulator.php @@ -24,6 +24,7 @@ use Antlr\Antlr4\Runtime\Error\Exceptions\RecognitionException; use Antlr\Antlr4\Runtime\IntervalSet; use Antlr\Antlr4\Runtime\IntStream; +use Antlr\Antlr4\Runtime\LoggerProvider; use Antlr\Antlr4\Runtime\Parser; use Antlr\Antlr4\Runtime\ParserRuleContext; use Antlr\Antlr4\Runtime\PredictionContexts\PredictionContext; @@ -35,6 +36,7 @@ use Antlr\Antlr4\Runtime\Utils\BitSet; use Antlr\Antlr4\Runtime\Utils\DoubleKeyMap; use Antlr\Antlr4\Runtime\Utils\Set; +use Psr\Log\LoggerInterface as Logger; /** * The embodiment of the adaptive LL(*), ALL(*), parsing strategy. @@ -231,6 +233,8 @@ */ final class ParserATNSimulator extends ATNSimulator { + public static bool $traceAtnSimulation = false; + protected Parser $parser; /** @var array */ @@ -260,6 +264,8 @@ final class ParserATNSimulator extends ATNSimulator protected ?DFA $dfa = null; + private Logger $logger; + /** * @param array $decisionToDFA */ @@ -273,6 +279,7 @@ public function __construct( $this->parser = $parser; $this->decisionToDFA = $decisionToDFA; + $this->logger = LoggerProvider::getLogger(); } public function reset(): void @@ -296,6 +303,18 @@ public function clearDFA(): void */ public function adaptivePredict(TokenStream $input, int $decision, ParserRuleContext $outerContext): int { + if (self::$traceAtnSimulation) { + $this->logger->debug( + 'adaptivePredict decision {decision} exec LA(1)=={token} line {line}:{pos}', + [ + 'decision' => $decision, + 'token' => $this->getTokenName($input->LA(1)), + 'line' => $input->LT(1)?->getLine(), + 'pos' => $input->LT(1)?->getCharPositionInLine(), + ], + ); + } + $this->input = $input; $this->startIndex = $input->getIndex(); $this->outerContext = $outerContext; @@ -404,6 +423,19 @@ public function execATN( int $startIndex, ParserRuleContext $outerContext, ): ?int { + if (self::$traceAtnSimulation) { + $this->logger->debug( + 'execATN decision {decision}, DFA state {state}, LA(1)=={token} line {line}:{pos}', + [ + 'decision' => $dfa->decision, + 'state' => $s0->__toString(), + 'token' => $this->getTokenName($input->LA(1)), + 'line' => $input->LT(1)?->getLine(), + 'pos' => $input->LT(1)?->getCharPositionInLine(), + ], + ); + } + $previousD = $s0; $t = $input->LA(1); @@ -655,6 +687,12 @@ protected function execATNWithFullContext( int $startIndex, ParserRuleContext $outerContext, ): int { + if (self::$traceAtnSimulation) { + $this->logger->debug('execATNWithFullContext {state}', [ + 'state' => $s0->__toString(), + ]); + } + $fullCtx = true; $foundExactAmbig = false; $reach = null; @@ -890,15 +928,18 @@ protected function computeReachSet(ATNConfigSet $closure, int $t, bool $fullCtx) * multiple alternatives are viable.*/ if ($skippedStopStates !== null && (!$fullCtx || !PredictionMode::hasConfigInRuleStopState($reach))) { - if (\count($skippedStopStates) === 0) { - throw new \LogicException('Skipped stop states cannot be empty.'); - } - foreach ($skippedStopStates as $lValue) { $reach->add($lValue, $this->mergeCache); } } + if (self::$traceAtnSimulation) { + $this->logger->debug('computeReachSet {closure} -> {reach}', [ + 'closure' => $closure->__toString(), + 'reach' => $reach->__toString(), + ]); + } + if ($reach->isEmpty()) { return null; } @@ -964,6 +1005,13 @@ protected function computeStartState(ATNState $p, RuleContext $ctx, bool $fullCt $initialContext = PredictionContext::fromRuleContext($this->atn, $ctx); $configs = new ATNConfigSet($fullCtx); + if (self::$traceAtnSimulation) { + $this->logger->debug('computeStartState from ATN state {state} initialContext={initialContext}', [ + 'state' => $p->__toString(), + 'initialContext' => $initialContext->__toString(), + ]); + } + foreach ($p->getTransitions() as $i => $t) { $c = new ATNConfig(null, $t->target, $initialContext, null, $i + 1); $closureBusy = new Set(); @@ -1464,6 +1512,12 @@ protected function closureCheckingStopState( int $depth, bool $treatEofAsEpsilon, ): void { + if (self::$traceAtnSimulation) { + $this->logger->debug('closure({config})', [ + 'config' => $config->toString(true), + ]); + } + if ($config->state instanceof RuleStopState) { // We hit rule end. If we have context info, use it run thru all possible stack tops in ctx $context = $config->context; @@ -2150,6 +2204,12 @@ protected function addDFAState(DFA $dfa, DFAState $D): DFAState $existing = $dfa->states->get($D); if ($existing instanceof DFAState) { + if (self::$traceAtnSimulation) { + $this->logger->debug('addDFAState {state} exists', [ + 'state' => $D->__toString(), + ]); + } + return $existing; } @@ -2160,6 +2220,12 @@ protected function addDFAState(DFA $dfa, DFAState $D): DFAState $D->configs->setReadonly(true); } + if (self::$traceAtnSimulation) { + $this->logger->debug('addDFAState new {state}', [ + 'state' => $D->__toString(), + ]); + } + $dfa->states->add($D); return $D; diff --git a/src/LoggerProvider.php b/src/LoggerProvider.php new file mode 100644 index 0000000..5bb5cd1 --- /dev/null +++ b/src/LoggerProvider.php @@ -0,0 +1,26 @@ +getByTwoKeys($a, $b); if ($previous !== null) { + if (ParserATNSimulator::$traceAtnSimulation) { + LoggerProvider::getLogger() + ->debug('mergeArrays a={a},b={b} -> previous', [ + 'a' => $a->__toString(), + 'b' => $b->__toString(), + ]); + } + return $previous; } $previous = $mergeCache->getByTwoKeys($b, $a); if ($previous !== null) { + if (ParserATNSimulator::$traceAtnSimulation) { + LoggerProvider::getLogger() + ->debug('mergeArrays a={a},b={b} -> previous', [ + 'a' => $a->__toString(), + 'b' => $b->__toString(), + ]); + } + return $previous; } } @@ -500,6 +518,14 @@ public static function mergeArrays( $mergeCache->set($a, $b, $a); } + if (ParserATNSimulator::$traceAtnSimulation) { + LoggerProvider::getLogger() + ->debug('mergeArrays a={a},b={b} -> a', [ + 'a' => $a->__toString(), + 'b' => $b->__toString(), + ]); + } + return $a; } @@ -508,6 +534,14 @@ public static function mergeArrays( $mergeCache->set($a, $b, $b); } + if (ParserATNSimulator::$traceAtnSimulation) { + LoggerProvider::getLogger() + ->debug('mergeArrays a={a},b={b} -> b', [ + 'a' => $a->__toString(), + 'b' => $b->__toString(), + ]); + } + return $b; } @@ -515,6 +549,15 @@ public static function mergeArrays( $mergeCache->set($a, $b, $M); } + if (ParserATNSimulator::$traceAtnSimulation) { + LoggerProvider::getLogger() + ->debug('mergeArrays a={a},b={b} -> M', [ + 'a' => $a->__toString(), + 'b' => $b->__toString(), + 'M' => $M->__toString(), + ]); + } + return $M; } diff --git a/src/StdoutMessageLogger.php b/src/StdoutMessageLogger.php new file mode 100644 index 0000000..58d2af4 --- /dev/null +++ b/src/StdoutMessageLogger.php @@ -0,0 +1,33 @@ + $context + */ + public function log($level, \Stringable|string $message, array $context = []): void + { + \fwrite(\STDOUT, self::formatMessage($message, $context) . \PHP_EOL); + } + + /** + * @param array $context + */ + private static function formatMessage(\Stringable|string $message, array $context): string + { + $replace = []; + foreach ($context as $key => $val) { + $replace['{' . $key . '}'] = $val; + } + + return \strtr((string) $message, $replace); + } +} diff --git a/src/Utils/Pair.php b/src/Utils/Pair.php index fc80417..3a3c5a3 100644 --- a/src/Utils/Pair.php +++ b/src/Utils/Pair.php @@ -38,6 +38,14 @@ public function hashCode(): int public function __toString(): string { - return \sprintf('%s, %s', (string) $this->a, (string) $this->b); + return \sprintf( + '%s, %s', + $this->a === null + ? 'null' + : ($this->a instanceof \Stringable ? (string) $this->a : $this->a::class), + $this->b === null + ? 'null' + : ($this->b instanceof \Stringable ? (string) $this->b : $this->b::class), + ); } }